🧹 Clean frontend files - Merge pull request #25 from linagora/clean-frontend-files

🧹 Clean frontend files
This commit is contained in:
Romaric Mourgues
2023-04-18 15:47:31 +02:00
committed by GitHub
556 changed files with 1119 additions and 32124 deletions
+3 -3
View File
@@ -6,13 +6,13 @@ import { RecoilRoot } from 'recoil';
import MobileRedirect from './components/mobile-redirect';
import RouterServices, { RouteType } from './features/router/services/router-service';
import ErrorBoundary from 'app/views/error/error-boundary';
import ErrorBoundary from '@views/error/error-boundary';
import InitService from './features/global/services/init-service';
import useTimeout from 'app/features/global/hooks/use-timeout';
import useTimeout from '@features/global/hooks/use-timeout';
import ApplicationLoader from './components/loader/application-loader';
import DebugState from './components/debug/debug-state';
import 'app/styles/index.less';
import '@styles/index.less';
const delayMessage = 5000;
@@ -6,7 +6,13 @@ import A from './index';
export default {
title: '@atoms/link',
component: A,
decorators: [(Story) => (<MemoryRouter><Story/></MemoryRouter>)]
decorators: [
Story => (
<MemoryRouter>
<Story />
</MemoryRouter>
),
],
} as ComponentMeta<typeof A>;
const Template: ComponentStory<typeof A> = args => <A {...args} />;
@@ -15,7 +21,7 @@ export const Default = Template.bind({});
Default.args = {
href: 'https://www.google.com',
children: 'Link',
}
};
export const noColor = Template.bind({});
noColor.args = {
@@ -1,18 +0,0 @@
.addUserButton {
color: var(--primary);
.icon {
.iconBox {
width: 18px;
height: 18px;
background-color: var(--primary-background);
border-radius: 4px;
padding-left: 2px;
.icon-unicon {
color: var(--primary) !important;
}
}
color: var(--primary) !important ;
}
}
@@ -1,40 +0,0 @@
import React from 'react';
import Icon from 'app/components/icon/icon.jsx';
import Languages from 'app/features/global/services/languages-service';
import './add-user-button.scss';
import { useRecoilState } from 'recoil';
import { invitationState } from 'app/features/invitation/state/invitation';
import { useInvitationUsers } from 'app/features/invitation/hooks/use-invitation-users';
import AccessRightsService from 'app/features/workspace-members/services/workspace-members-access-rights-service';
import { useCurrentWorkspace } from 'app/features/workspaces/hooks/use-workspaces';
export default () => {
const [, setInvitationOpen] = useRecoilState(invitationState);
const { allowed_guests, allowed_members } = useInvitationUsers();
const { workspace } = useCurrentWorkspace();
return AccessRightsService.hasLevel(workspace?.id, 'moderator') &&
(allowed_guests > 0 || allowed_members > 0) ? (
<div
className="channel addUserButton"
onClick={() => {
setInvitationOpen(true);
}}
>
<div className="icon">
<div className="iconBox">
<Icon type="plus" />
</div>
</div>
<div className="text">
{Languages.t(
'scenes.app.popup.workspaceparameter.pages.collaboraters_adding_button',
[],
'Ajouter des collaborateurs',
)}
</div>
</div>
) : (
<></>
);
};
@@ -1,199 +0,0 @@
/* eslint-disable react/prop-types */
import React, { Component } from 'react';
import File from 'components/drive/file';
import FilePicker from 'components/drive/file-picker/file-picker.jsx';
import TaskPicker from 'components/task-picker/task-picker.jsx';
import Menu from 'components/menus/menu.jsx';
import Icon from 'app/components/icon/icon.jsx';
import Button from 'components/buttons/button.jsx';
import UploadZone from 'components/uploads/upload-zone';
import MenusManager from 'app/components/menus/menus-manager.jsx';
import Workspaces from 'app/deprecated/workspaces/workspaces.jsx';
import Languages from 'app/features/global/services/languages-service';
import './attachment-picker.scss';
export default class AttachmentPicker extends Component {
/*
props : {
readOnly : bool
attachments : []
onChange
className
}
*/
getIcon(att) {
if (att.type.toLocaleLowerCase() === 'event') {
return 'calendar-alt';
}
if (att.type.toLocaleLowerCase() === 'file') {
return 'folder';
}
if (att.type.toLocaleLowerCase() === 'task') {
return 'check-square';
}
}
addAttachment(attachment) {
var attachments = this.props.attachments || [];
attachments.push(attachment);
if (this.props.onChange) {
this.props.onChange(attachments);
}
}
removeAttachment(attachment) {
var attachments = this.props.attachments || [];
var index = attachments.indexOf(attachment);
if (index >= 0) {
attachments.splice(index, 1);
if (this.props.onChange) {
this.props.onChange(attachments);
}
}
}
render() {
return (
<div className={'attachmentPicker ' + (this.props.className || '')}>
<div className="attachments">
{(Object.values(this.props.attachments || {}) || []).map(att => {
if (att.type === 'file') {
var additionalMenu = [];
if (!this.props.readOnly) {
additionalMenu = [
{
type: 'menu',
text: Languages.t(
'components.attachmentpicker.remove_attach',
[],
'Remove attachment',
),
onClick: () => {
this.removeAttachment(att);
},
},
];
}
return (
<div className="attachment attachment_file drive_view list" key={`attachment_file-${att.id}`}>
<File
data={{ id: att.id || '' }}
additionalMenu={additionalMenu}
notInDrive={true}
style={{ marginBottom: 0 }}
/>
</div>
);
}
return (
<div className="attachment" key={att.id}>
<Icon className="app-icon" type={this.getIcon(att)} />
{att.name}
{!this.props.readOnly && (
<Icon
className="remove"
type="times"
onClick={() => {
this.removeAttachment(att);
}}
/>
)}
</div>
);
})}
</div>
{!this.props.readOnly && (
<Menu
style={{ display: 'inline-block' }}
menu={[
{
type: 'menu',
text: Languages.t('components.attachmentpicker.file', [], 'File'),
icon: 'file',
submenu: [
{
type: 'menu',
icon: 'desktop',
text: Languages.t(
'components.attachmentpicker.from_computer',
[],
'From computer',
),
onClick: () => {
this.upload_zone.open();
MenusManager.closeMenu();
},
},
{
type: 'menu',
icon: 'folder',
text: Languages.t(
'components.attachmentpicker.from_tdrive',
[],
'From Tdrive Documents',
),
submenu: [
{
type: 'react-element',
reactElement: (
<FilePicker
mode={'select_file'}
onChoose={res => {
this.addAttachment({ type: 'file', id: res.id, name: res.name });
MenusManager.closeMenu();
}}
initialDirectory={{ id: '' }}
/>
),
},
],
},
],
},
{
type: 'menu',
text: Languages.t('scenes.apps.tasks.task', [], 'Task'),
icon: 'check-square',
submenu: [
{
type: 'react-element',
reactElement: (
<TaskPicker
mode={'select_task'}
onChoose={res => {
this.addAttachment({ type: 'task', id: res.id, name: res.title });
MenusManager.closeMenu();
}}
/>
),
},
],
},
]}
>
{' '}
<Button className="small secondary-text right-margin">
<Icon type="plus" className="m-icon-small" />{' '}
{Languages.t(
'components.attachmentpicker.add_attachment',
[],
'Ajouter des pièces jointes',
)}
</Button>
</Menu>
)}
<UploadZone
ref={node => (this.upload_zone = node)}
disableClick
parent={''}
driveCollectionKey={'attachment_' + Workspaces.currentWorkspaceId}
uploadOptions={{ workspace_id: Workspaces.currentWorkspaceId, detached: true }}
onUploaded={res => {
this.addAttachment({ type: 'file', id: res.id, name: res.name });
}}
multiple={false}
/>
</div>
);
}
}
@@ -1,39 +0,0 @@
.attachmentPicker {
.attachment {
display: inline-block;
font-size: 12px;
color: var(--black);
border: solid 1px var(--grey-background);
height: 32px;
box-sizing: border-box;
border-radius: var(--border-radius-large);
padding: 4px;
padding-right: 8px;
line-height: 24px;
margin-right: 8px;
margin-bottom: 8px;
&.attachment_file {
border: 0;
padding: 0;
height: auto;
}
}
i.app-icon {
margin-right: 4px;
margin-left: 4px;
font-size: 14px;
position: relative;
top: -1px;
vertical-align: top;
}
.remove {
opacity: 0.5;
cursor: pointer;
&:hover {
opacity: 1;
}
}
}
@@ -1,159 +0,0 @@
/* eslint-disable react/prop-types */
/* eslint-disable react/jsx-key */
import React from 'react';
import './calendar-selector.scss';
import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js';
import Select from 'components/select/select.jsx';
import MenusManager from 'app/components/menus/menus-manager.jsx';
import WorkspacesService from 'app/deprecated/workspaces/workspaces.jsx';
import Languages from 'app/features/global/services/languages-service';
export default class CalendarSelector extends React.Component {
constructor(props) {
super();
this.props = props;
this.state = {
selected: [],
};
}
delete(item) {
var list = this.props.value.map(item => item.calendar_id);
const index = list.indexOf(item.id);
if (index >= 0) {
list.splice(index, 1);
this.change(list);
}
MenusManager.closeMenu();
}
openMenu(evt, item) {
if (this.props.readonly) {
return;
}
MenusManager.openMenu(
[
{
className: 'error',
icon: 'trash',
text: Languages.t('components.calendar.calendarselector.remove', [], 'Retirer'),
onClick: () => {
this.delete(item);
},
},
],
{ x: evt.clientX, y: evt.clientY, width: 150 },
'bottom',
);
}
change(list) {
if (this.props.onChange)
this.props.onChange(
list
.map(id => {
var cal = Collections.get('calendars').find(id);
if (!cal) {
return undefined;
}
return {
calendar_id: cal.id,
workspace_id: cal.workspace_id,
};
})
.filter(item => item),
);
}
render() {
return (
<div
className={
'calendar_selector_container ' +
(this.props.medium ? 'medium ' : '') +
(this.props.small ? 'small ' : '') +
(this.props.big ? 'big ' : '') +
this.props.className
}
>
<div className="selected_calendars_list calendar_selector_part">
{this.props.value.map(ws_cal_id => {
var item = Collections.get('calendars').find(ws_cal_id.calendar_id);
if (!item || item.workspace_id !== WorkspacesService.currentWorkspaceId) {
return '';
}
return (
<div
ref={node => (this.node = node)}
onClick={evt => this.openMenu(evt, item)}
className={'calendar_item ' + (this.props.readonly ? '' : 'removable')}
>
<div className="calendar_color" style={{ background: item.color }} />
{'' + item.title}
</div>
);
})}
{(!this.props.value || this.props.value.length === 0) && (
<div className="smalltext right-margin" style={{ lineHeight: '40px' }}>
{Languages.t(
'components.calendar.calendarselector.no_workspace_calendar',
[],
"Aucun calendrier d'espace de travail.",
)}
</div>
)}
</div>
{!this.props.readonly && (
<div className="list_calendar_to_select">
<Select
medium={this.props.medium}
small={this.props.small}
big={this.props.big}
options={[
{
type: 'title',
text: Languages.t('scenes.apps.calendar.left.calendars', [], 'Calendriers'),
className: 'no-background',
},
].concat(
this.props.calendarList.map(item => {
return {
text: (
<div className="calendar_selector_part calendar_item">
<div className="calendar_color" style={{ background: item.color }} />
{item.title}
</div>
),
value: item.id,
};
}),
)}
onChange={v => {
var list = this.props.value.map(item => item.calendar_id);
var existe = false;
// eslint-disable-next-line array-callback-return
list.map(id => {
if (v === id) existe = true;
});
if (!existe) {
list.push(v);
}
//Only one calendar for now
if (!this.props.allowMultiple) {
list = [v];
}
this.change(list);
}}
/>
</div>
)}
</div>
);
}
}
@@ -1,61 +0,0 @@
.calendar_selector_container {
display: inline-block;
vertical-align: middle;
.selected_calendars_list {
display: inline-block;
}
.list_calendar_to_select {
display: inline-block;
vertical-align: top;
.select {
display: inline-block;
vertical-align: top;
}
}
&.medium {
.calendar_item {
height: 40px;
padding: 4px 12px;
}
}
}
.calendar_selector_part {
.calendar_item {
height: 32px;
box-sizing: border-box;
line-height: 32px;
padding-left: 8px;
padding-right: 8px;
font-size: 14px;
display: inline-block;
background-color: var(--grey-background);
border-radius: var(--border-radius-base);
margin-right: 8px;
margin-bottom: 8px;
text-transform: capitalize;
&.removable {
cursor: pointer;
&:hover {
background: var(--primary-background);
}
}
}
.calendar_color {
width: 12px;
height: 12px;
border-radius: 6px;
display: inline-block;
margin: 10px 0;
vertical-align: top;
background: var(--green);
margin-right: 8px;
}
}
@@ -1,423 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
import Tooltip from 'components/tooltip/tooltip.jsx';
import moment from 'moment';
import DateTimeUtils from 'app/features/global/utils/datetime.js';
import UserService from 'app/features/users/services/current-user-service';
import Globals from 'app/features/global/services/globals-tdrive-app-service';
import DayPicker from './day-picker/day-picker.jsx';
import MenusManager from 'app/components/menus/menus-manager.jsx';
import Input from 'components/inputs/input.jsx';
import Icon from 'app/components/icon/icon.jsx';
import './date-picker.scss';
export default class DatePicker extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.display_format = 'D MMM YYYY';
this.old_props_ts = props.ts;
this.state = {
time_ts: props.ts,
time_string: moment(new Date(props.ts * 1000)).format(this.display_format),
};
this.user_language =
(UserService.getCurrentUser() || {}).language || Globals.window.navigator.language;
this.months_names = [
'jan',
'feb',
'mar',
'apr',
'may',
'jun',
'jul',
'aug',
'sep',
'oct',
'nov',
'dec',
].map(m => m.toLocaleLowerCase());
this.months_long_names = [
'january',
'february',
'march',
'april',
'may',
'june',
'july',
'august',
'september',
'october',
'november',
'december',
].map(m => m.toLocaleLowerCase());
this.months_names = this.months_names
.concat(
moment.localeData().monthsShort() || [
'jan',
'feb',
'mar',
'apr',
'may',
'jun',
'jul',
'aug',
'sep',
'oct',
'nov',
'dec',
],
)
.map(m => m.toLocaleLowerCase());
this.months_long_names = this.months_long_names
.concat(moment.localeData().months())
.map(m => m.toLocaleLowerCase());
this.months_names = this.months_names
.concat(moment.localeData(this.user_language).monthsShort())
.map(m => m.toLocaleLowerCase());
this.months_long_names = this.months_long_names
.concat(moment.localeData(this.user_language).months())
.map(m => m.toLocaleLowerCase());
if (
Globals.window.navigator &&
Globals.window.navigator.language &&
Globals.window.navigator.language !== this.user_language
) {
this.months_names = this.months_names.concat(
(moment.localeData(Globals.window.navigator.language).monthsShort() || []).map(m =>
m.toLocaleLowerCase(),
),
);
this.months_long_names = this.months_long_names.concat(
(moment.localeData(Globals.window.navigator.language).months() || []).map(m =>
m.toLocaleLowerCase(),
),
);
}
this.all_months = this.months_long_names.concat(this.months_names);
this.focused = false;
}
componentWillUnmount() {
if (this.helper_open) {
MenusManager.closeMenu();
}
}
shouldComponentUpdate(nextProps, nextStates) {
if (nextProps.ts !== this.old_props_ts) {
this.old_props_ts = nextProps.ts;
nextStates.time_ts = nextProps.ts;
if (!this.focused) {
nextStates.time_string = moment(new Date(nextProps.ts * 1000)).format(this.display_format);
}
}
return true;
}
process(string) {
var d, m, y;
var error = false;
if (string) {
var original_string = string;
//Try to find year
var year = string.match(/([0-9]{4,4})/);
if (year && year[1]) {
y = parseInt(year[1]);
string = string.replace(year[1], '');
}
//Try to find month as text
var sentences = string.match(/(^|[0-9])([^0-9]+)($|[0-9])/);
var in_text_month = false;
if (sentences && sentences[1] !== undefined) {
sentences.slice(1).forEach(s => {
// eslint-disable-next-line no-useless-escape
s = s.replace(/[0-9-'`~!@#$%^&*()_|+=?;:'",.<>\{\}\[\]\\\/]/gi, '');
s = s.trim();
s = s.toLocaleLowerCase();
if (s) {
var found = false;
this.all_months.forEach((m, i) => {
if (!found && m.indexOf(s) === 0) {
found = (i % 12) + 1;
}
});
if (found !== false) {
in_text_month = found;
string = string.replace(s, '');
}
}
});
}
if (in_text_month) {
m = in_text_month;
}
if (m && y) {
d = parseInt(string.replace(/[^0-9]/, ''));
} else {
//Get all numbers
var numbers = string.match(/[+-]?\d+(?:\.\d+)?/g);
if (numbers && numbers.length >= 1) {
if (y) {
//Only got year
if (numbers.length === 1) {
m = parseInt(numbers[0]);
} else {
//>=2
if (DateTimeUtils.isDateFirstInFormat()) {
d = parseInt(numbers[0]);
m = parseInt(numbers[1]);
} else {
d = parseInt(numbers[1]);
m = parseInt(numbers[0]);
}
if (m > 12) {
var tmp = d;
d = m;
m = tmp;
}
}
} else if (m) {
//Only got month
if (numbers.length === 1) {
d = parseInt(numbers[0]);
} else {
d = parseInt(numbers[0]);
y = parseInt(numbers[1]);
if (y < 100 && y >= 50) {
y += 1900;
} else if (y < 50) {
y += 2000;
}
}
} else {
//Got nothing
if (numbers.length === 1) {
y = parseInt(numbers[0]);
d = 1;
m = 1;
if (y < 100 && y >= 50) {
y += 1900;
} else if (y < 50) {
y += 2000;
}
} else if (numbers.length === 2) {
if (Math.max(parseInt(numbers[0]), parseInt(numbers[1])) > 12) {
y = Math.max(d, m);
m = Math.min(d, m);
} else {
d = parseInt(numbers[0]);
m = parseInt(numbers[1]);
}
if (m > 12) {
// eslint-disable-next-line no-redeclare
var tmp = m;
m = d;
d = tmp;
}
} else if (numbers.length >= 3) {
if (DateTimeUtils.isDateFirstInFormat()) {
d = parseInt(numbers[0]);
m = parseInt(numbers[1]);
y = parseInt(numbers[2]);
} else {
d = parseInt(numbers[1]);
m = parseInt(numbers[0]);
y = parseInt(numbers[2]);
}
}
if (y < 100 && y >= 50) {
y += 1900;
} else if (y < 50) {
y += 2000;
}
}
}
}
//Last check date is correct
if (!y || !m || !d || isNaN(y) || isNaN(m) || isNaN(d)) {
if ((y && !isNaN(y)) || (m && !isNaN(m)) || (d && !isNaN(d))) {
//If partial input, do not show error
if (y && !isNaN(y)) {
m = m || 1;
d = d || 1;
} else if (m && !isNaN(m)) {
y = new Date().getFullYear() + (m <= new Date().getMonth() + 1 ? 1 : 0);
d = d || 1;
} else if (d && !isNaN(d)) {
m = 1;
y = y || new Date().getFullYear() + (m <= new Date().getMonth() + 1 ? 1 : 0);
}
} else {
error = true;
}
}
if (y < 1950) {
error = true;
}
if (m < 1 || m > 12) {
error = true;
}
// eslint-disable-next-line no-redeclare
var tmp = new Date();
tmp.setMonth(m - 1);
tmp.setFullYear(y);
tmp.setDate(d);
if (d < 1 || d > 31 || tmp.getDate() !== d) {
error = true;
}
} else {
error = true;
}
this.setState({ time_string: original_string });
if (!error) {
// eslint-disable-next-line no-redeclare
var d = moment(d + '-' + m + '-' + y, 'DD-MM-YYYY').toDate();
this.changeDate(d);
} else {
this.setState({ error: true });
this.setState({});
}
}
changeDate(d, changeInput) {
var date = new Date(this.state.time_ts * 1000);
date.setDate(d.getDate());
date.setMonth(d.getMonth());
date.setFullYear(d.getFullYear());
this.setState({ time_ts: date.getTime() / 1000 });
this.setState({ error: false });
this.setState({
time_string_formatted: moment(this.state.time_ts * 1000).format(this.display_format),
});
if (changeInput) {
this.setState({ time_string: this.state.time_string_formatted });
}
this.props.onChange && this.props.onChange(this.state.time_ts);
this.setState({});
this.input.focus();
}
focus() {
this.closeMenuTimeout && clearTimeout(this.closeMenuTimeout);
this.focused = true;
if (!this.state.time_ts) {
this.setState({ time_ts: new Date().getTime() / 1000 });
}
if (!this.helper_open) {
this.helper_open = true;
var pos = window.getBoundingClientRect(this.input);
pos.x = pos.x || pos.left;
pos.y = pos.y || pos.top;
MenusManager.openMenu(
[
{
type: 'react-element',
reactElement: () => (
<div
style={{ padding: '20px 24px', margin: -16 }}
onMouseDown={() => {
this.cancelBlur = true;
}}
>
<DayPicker
value={moment(this.state.time_ts * 1000)}
onChange={value => {
var ts = value._d.getTime();
MenusManager.closeMenu();
setTimeout(() => {
this.changeDate(new Date(ts), true);
this.cancelBlur = false;
this.input.blur();
}, 100);
}}
onClick={() => this.input.focus()}
/>
</div>
),
},
],
{ x: pos.left + pos.width / 2, y: pos.bottom },
'bottom',
{ allowClickOut: false },
);
}
}
blur() {
if (this.cancelBlur) {
this.cancelBlur = false;
return;
}
this.focused = false;
this.setState({ time_string: this.state.time_string_formatted, error: false });
this.closeMenuTimeout && clearTimeout(this.closeMenuTimeout);
this.closeMenuTimeout = setTimeout(() => {
MenusManager.closeMenu();
this.helper_open = false;
}, 50);
this.props.onChangeBlur && this.props.onChangeBlur(this.state.time_ts);
this.props.onChange && this.props.onChange(this.state.time_ts);
}
render() {
return (
<div className={'date_selector ' + this.props.className} style={{ display: 'inline-block' }}>
<Tooltip position="top" tooltip="Invalide" overable={false} visible={this.state.error}>
<Input
onEchap={() => this.input.blur()}
onEnter={() => this.input.blur()}
onBlur={() => this.blur()}
onFocus={() => this.focus()}
placeholder={'No date'}
value={this.state.time_ts ? this.state.time_string : ''}
style={{ maxWidth: 108 }}
onChange={evt => this.process(evt.target.value)}
refInput={node => (this.input = node)}
big={this.props.big}
medium={this.props.medium}
small={this.props.small}
/>
{this.props.withReset && (
<div
className="reset_date"
onClick={() => {
this.setState({ time_ts: false });
this.props.onChangeBlur(this.state.time_ts);
this.setState({});
}}
>
<Icon type="trash" />
</div>
)}
</Tooltip>
</div>
);
}
}
@@ -1,18 +0,0 @@
.date_selector {
.reset_date {
width: 16px;
height: 16px;
position: absolute;
right: 8px;
top: 12px;
background: #eee;
border-radius: var(--border-radius-large);
font-size: 12px;
color: #444;
cursor: pointer;
&:hover {
background: #ddd;
}
}
}
@@ -1,164 +0,0 @@
/* eslint-disable react/prop-types */
import React, { Component } from 'react';
import Icon from 'app/components/icon/icon.jsx';
import moment from 'moment';
import './day-picker.scss';
export default class DayPicker extends Component {
/*
this.props :{
onChange(day)
value : Array of selected days
}
*/
constructor(props) {
super(props);
this.state = {
days: [],
currentDate: moment(),
};
this.openSelected = false;
window.moment = moment;
}
componentDidMount() {
this.setState(this.updateDay());
}
UNSAFE_componentWillUpdate(nextProps, nextState) {
if (moment(nextProps.value).valueOf() != this.oldProp) {
var obj = this.updateDay(nextProps.value);
nextState.days = obj.days;
nextState.currentDate = obj.currentDate;
}
this.oldProp = moment(nextProps.value).valueOf();
}
updateDay(date) {
var searchedDate = date || moment();
var days = [];
var first_day_of_month = moment(searchedDate).startOf('month').startOf('week');
for (var i = 0; i < 35; i++) {
days.push(moment(moment(first_day_of_month).add(i, 'days')));
}
return {
days: days,
currentDate: searchedDate,
};
}
isSelected(day) {
if (this.props.value) {
if (
Array.isArray(this.props.value) &&
this.props.value.length == 2 &&
this.props.value[0] &&
this.props.value[1]
) {
return day.isBetween(this.props.value[0], this.props.value[1], 'day', '[]');
} else if (
Array.isArray(this.props.value) &&
this.props.value.length >= 1 &&
this.props.value[0]
) {
return day.isSame(this.props.value[0], 'day');
} else if (!Array.isArray(this.props.value) && this.props.value) {
return day.isSame(this.props.value, 'day');
}
}
return false;
}
isLast(day) {
if (this.props.value) {
if (
Array.isArray(this.props.value) &&
this.props.value.length == 2 &&
this.props.value[0] &&
this.props.value[1]
) {
return day.isSame(this.props.value[1], 'day');
} else if (
Array.isArray(this.props.value) &&
this.props.value.length >= 1 &&
this.props.value[0]
) {
return day.isSame(this.props.value[0], 'day');
} else if (!Array.isArray(this.props.value) && this.props.value) {
return day.isSame(this.props.value, 'day');
}
}
return false;
}
render() {
var last_week = -1;
return (
<div
className="dayPicker"
onClick={() => this.props.onClick && this.props.onClick()}
onMouseDown={this.props.onMouseDown}
>
<div className="titleDayPicker">
<div className="month">{moment(this.state.currentDate).format('MMMM YYYY')}</div>
<div className="chevron">
<div
className="move_icon"
onClick={() => {
this.setState(this.updateDay(moment(this.state.currentDate).subtract(1, 'months')));
}}
>
<Icon type="angle-left" />
</div>
<div
className="move_icon"
onClick={() => {
this.setState(this.updateDay(moment(this.state.currentDate).add(1, 'months')));
}}
>
<Icon type="angle-right" />
</div>
</div>
</div>
<div className="days">
<div className="dayName" key={'wn'}>
Wk
</div>
{this.state.days.map((day, index) => {
if (index < 7) {
return (
<div className="dayName" key={day.format()}>
{day.format('ddd')}
</div>
);
}
})}
{this.state.days.map(day => {
var list = [];
if (day.week() != last_week) {
last_week = day.week();
list.push(<div className="weeknumber">{last_week}</div>);
}
list.push(
<div
key={day.format()}
onClick={() => {
if (this.props.onChange) {
this.props.onChange(day);
}
}}
className={
'day ' +
(day.month() == this.state.currentDate.month() ? '' : 'notInMonth') +
' ' +
(day.format('YYYY MM DD') == moment().format('YYYY MM DD') ? 'today' : '') +
' ' +
(this.isSelected(day) ? 'selected' : '') +
' ' +
(this.isLast(day) ? 'last' : '')
}
>
{day.date()}
</div>,
);
return list;
})}
</div>
</div>
);
}
}
@@ -1,97 +0,0 @@
.dayPicker {
width: 200px;
font-size: 14px;
font-weight: 500;
.titleDayPicker {
display: flex;
margin-top: -8px;
margin-bottom: -4px;
.month {
display: inline-block;
text-transform: capitalize;
line-height: 30px;
}
.chevron {
flex: 1;
text-align: right;
display: inline-block;
.move_icon {
display: inline-block;
font-size: 14px;
opacity: 1;
padding: 5px;
border-radius: var(--border-radius-base);
}
.move_icon:hover {
cursor: pointer;
background: rgba(55, 53, 47, 0.08);
}
}
}
.days {
.weeknumber {
background: #dfdfdf;
height: 26px;
line-height: 26px;
vertical-align: top;
display: inline-block;
font-size: 12px;
color: #000;
opacity: 0.3;
padding: 0 1px;
box-sizing: border-box;
width: 12.5%;
text-align: center;
}
.dayName {
display: inline-block;
width: 12.5%;
text-align: center;
height: 28px;
line-height: 28px;
font-size: 11px;
border-radius: var(--border-radius-base);
opacity: 0.6;
}
.dayName:first-child {
opacity: 0.3;
}
.day {
display: inline-block;
width: 12.5%;
text-align: center;
height: 24px;
line-height: 24px;
margin-bottom: 2px;
border-radius: var(--border-radius-base);
}
.day:not(.selected):hover {
background: rgba(45, 156, 219, 0.2);
}
.day:hover {
cursor: pointer;
}
.notInMonth {
opacity: 0.5;
}
.selected + .selected:not(.last) {
border-radius: 0px;
}
.selected {
background: #827dff;
border-radius: var(--border-radius-base) 0px 0px var(--border-radius-base);
color: white;
}
.selected + .last {
border-radius: 0px 4px 4px 0px;
}
:not(.selected) + .last {
border-radius: var(--border-radius-base);
}
.today:not(.selected) {
color: rgb(235, 87, 87);
border-radius: var(--border-radius-base);
}
}
}
@@ -1,123 +0,0 @@
/* eslint-disable react/prop-types */
import React, { Component } from 'react';
import './event.scss';
import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js';
import Icon from 'app/components/icon/icon.jsx';
import Languages from 'app/features/global/services/languages-service';
import UserListManager from 'components/user-list-manager-depreciated/user-list-manager';
import moment from 'moment';
export default class Event extends Component {
constructor(props) {
super();
this.props = props;
}
render() {
var className = '';
var icon = '';
var event_duration = '';
var classNameUser = '';
if (this.props.inEvent) {
classNameUser = 'inEvent';
}
var event_name =
this.props.event.title || Languages.t('scenes.apps.drive.navigators.new_file.untitled');
var event_start = moment(parseInt(this.props.event.from) * 1000).format('LT');
var event_end = moment(parseInt(this.props.event.to) * 1000).format('LT');
var location = this.props.event.location || '';
if (event_start) event_duration = event_start;
if (event_end)
event_duration =
event_duration + Languages.t('components.calendar.event.to', [], ' à ') + event_end;
var duration = (parseInt(this.props.event.to) - parseInt(this.props.event.from)) / 60;
if (
(duration > 0 && duration <= 15) ||
this.props.event.type == 'deadline' ||
this.props.event.type == 'remind'
) {
className += ' size_15 ';
} else if ((duration > 15 && duration <= 30) || this.props.event.all_day) {
className += ' size_30';
if (location) event_duration += ', ' + location;
} else if (duration > 30 && duration <= 45) {
className += ' size_45';
} else if (duration > 45 && duration <= 60) {
className += ' size_60';
}
className += ' ' + this.props.event.type;
if (this.props.event.type == 'remind') {
icon = 'stopwatch';
} else if (this.props.event.type == 'deadline') {
icon = 'stopwatch-slash';
} else if (this.props.event.type == 'move') {
icon = 'car-sideview';
}
var users = [];
var emails = [];
(this.props.event.participants || []).map(obj => {
if (obj) {
var regex = /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
if (obj.user_id_or_mail.match(regex)) {
var user = Collections.get('users').find(obj.user_id_or_mail);
users.push(user);
} else {
var email = obj.user_id_or_mail;
emails.push(email);
}
}
});
return (
<div
className={'event_container ' + className + ' ' + classNameUser}
style={{ background: '' + this.props.getColor() }}
>
<div className={'block ' + className}>
{icon && <Icon type={'' + icon} className="icon" />}
<span className={'text title ' + className}>
{event_name}
<span style={{ fontWeight: 'normal' }}>
{(duration <= 30 || this.props.event.all_day) && ', ' + event_start}
</span>
</span>
</div>
{duration > 30 && !this.props.event.all_day && (
<div className={'time ' + className}>
<span className="text simple">{event_duration}</span>
</div>
)}
<div className={'place ' + className}>
<span className="text simple">{'' + location}</span>
</div>
<div className={'users ' + className}>
<UserListManager
noPlaceholder
users={(this.props.event.participants || []).map(u => {
return { id: u.user_id_or_mail };
})}
max={4}
readOnly
collapsed
medium
/>
</div>
</div>
);
}
}
@@ -1,144 +0,0 @@
.event_container {
margin: 0px;
width: auto;
height: auto;
border-radius: var(--border-radius-base);
color: var(--white);
box-sizing: border-box;
padding-left: 4px;
padding-right: 2px;
padding-top: 2px;
position: relative;
transition: box-shadow 0.2s;
cursor: pointer;
& > div {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&:hover {
box-shadow: 0 0px 1000px 0px rgba(0, 0, 0, 0.1) inset;
}
.icon {
display: inline-block;
font-size: 14px;
font-weight: 700;
margin-left: -2px;
}
.text {
&.title {
font-size: 12px;
font-weight: 700;
}
&.simple {
font-size: 11px;
font-weight: 500;
}
}
.time,
.place,
.description,
.users {
display: flex;
flex-direction: row;
text-overflow: ellipsis;
}
.users {
position: absolute;
bottom: 2px;
right: 2px;
display: flex;
align-items: center;
height: 28px;
.emoji-container {
width: 24px;
text-align: center;
}
.more {
font-size: 12px;
margin: 0 2px;
font-weight: 700;
opacity: 0.8;
}
}
.time.size_15,
.place.size_15,
.place.size_30,
.users.size_15,
.users.size_30,
.users.size_45 {
.user_head {
width: 12px !important;
height: 12px !important;
}
}
.place.size_45 {
margin-top: 8px;
}
.users > div {
border-radius: var(--border-radius-base);
margin-left: 2px;
}
//event
min-height: 60px;
&.size_15 {
min-height: 16px;
height: 16px;
font-size: 11px;
}
&.size_30 {
min-height: 24px;
height: 24px;
}
&.size_45 {
min-height: 32px;
height: 32px;
}
&.size_60 {
min-height: 48px;
}
.inEvent {
border-style: solid;
border: 0px;
}
//rappel
&.remind {
border-radius: var(--border-radius-large);
min-height: 18px;
}
//deadline
&.deadline {
border-top-left-radius: 1px;
border-top-right-radius: 1px;
box-shadow: 0 2px 0px 0px rgba(255, 0, 0, 0.5) inset;
min-height: 18px;
}
//deplacement
&.move {
border-radius: var(--border-radius-large);
border-top-left-radius: 4px;
}
}
@@ -1,121 +0,0 @@
/* eslint-disable react/prop-types */
/* eslint-disable react/no-direct-mutation-state */
import React, { Component } from 'react';
import Input from 'components/inputs/input.jsx';
import Tooltip from 'components/tooltip/tooltip.jsx';
import moment from 'moment';
import DateTimeUtils from 'app/features/global/utils/datetime.js';
import Languages from 'app/features/global/services/languages-service';
export default class TimeSelector extends Component {
constructor(props) {
super(props);
this.state = {
time_ts: props.ts,
time_string: moment(new Date(props.ts * 1000)).format(DateTimeUtils.getDefaultTimeFormat()),
};
this.old_ts = props.ts;
this.focus = false;
}
shouldComponentUpdate(nextProps, nextStates) {
if (nextProps.ts != this.old_ts) {
this.old_ts = nextProps.ts;
nextStates.time_ts = nextProps.ts;
if (!this.focus) {
nextStates.time_string = moment(new Date(nextProps.ts * 1000)).format(
DateTimeUtils.getDefaultTimeFormat(),
);
}
}
return true;
}
process(string) {
var h, m, am_pm;
var error = false;
if (string) {
var match = string.match(/^[^0-9]*([0-9]+)([^0-9]+)?([0-9]*)?(.*?)$/);
if (match) {
h = parseInt(match[1]);
m = parseInt(match[3] || '0');
am_pm = ((match[4] ? match[4] : match[2]) || '').toLocaleLowerCase();
if (am_pm && (am_pm[0] == 'p' || am_pm.indexOf('pm') >= 0)) {
am_pm = 'PM';
} else if (am_pm && (am_pm[0] == 'a' || am_pm.indexOf('am') >= 0)) {
am_pm = 'AM';
} else {
if (h == 12) {
am_pm = 'PM';
} else {
am_pm = 'AM';
}
}
} else {
error = true;
}
if (h > 12) {
am_pm = '';
}
if (h > 23) {
error = true;
}
if (m > 59) {
error = true;
}
} else {
error = true;
}
this.state.time_string = string;
if (!error) {
var d = moment(h + ':' + m + am_pm, 'LT').toDate();
var date = new Date(this.state.time_ts * 1000);
date.setHours(d.getHours());
date.setMinutes(d.getMinutes());
this.state.time_ts = date.getTime() / 1000;
this.state.error = false;
this.state.time_string_formatted = moment(this.state.time_ts * 1000).format(
DateTimeUtils.getDefaultTimeFormat(),
);
this.props.onChange && this.props.onChange(this.state.time_ts);
} else {
this.state.error = true;
}
this.setState({});
}
blur() {
this.focus = false;
this.setState({ time_string: this.state.time_string_formatted, error: false });
this.props.onChangeBlur && this.props.onChangeBlur(this.state.time_ts);
this.props.onChange && this.props.onChange(this.state.time_ts);
}
render() {
return (
<div className={'time_selector ' + this.props.className} style={{ display: 'inline-block' }}>
<Tooltip
position="top"
tooltip={Languages.t('components.workspace.calendar.invalid', [], 'Invalide')}
overable={false}
visible={this.state.error}
>
<Input
onBlur={() => this.blur()}
onFocus={() => (this.focus = true)}
value={this.state.time_string}
style={{ maxWidth: 94 }}
onChange={evt => this.process(evt.target.value)}
/>
</Tooltip>
</div>
);
}
}
@@ -1,44 +0,0 @@
import { Modal, ModalContent } from 'app/atoms/modal';
import {
channelAttachmentListState,
activeChannelAttachementListTabState,
} from 'app/features/channels/state/channel-attachment-list';
import Languages from 'app/features/global/services/languages-service';
import Tab from 'app/molecules/tabs';
import React from 'react';
import { useRecoilState } from 'recoil';
import ChannelFiles from './parts/channel-files';
import ChannelMedias from './parts/channel-medias';
enum Tabs {
Medias = 0,
Files = 1,
}
export default (): React.ReactElement => {
const [open, setOpen] = useRecoilState(channelAttachmentListState);
const [activeTab, setActiveTab] = useRecoilState(activeChannelAttachementListTabState);
return (
<Modal open={open} onClose={() => setOpen(false)} className="sm:w-[60vw] sm:max-w-2xl">
<ModalContent textCenter title={Languages.t('components.channel_attachement_list.title')}>
<Tab
tabs={[
<div key="media">
<div className="flex">
{Languages.t('components.channel_attachement_list.medias')}
</div>
</div>,
<div key="files">
<div className="flex">{Languages.t('components.channel_attachement_list.files')}</div>
</div>,
]}
selected={activeTab}
onClick={index => setActiveTab(index)}
/>
{activeTab === Tabs.Medias && <ChannelMedias />}
{activeTab === Tabs.Files && <ChannelFiles />}
</ModalContent>
</Modal>
);
};
@@ -1,176 +0,0 @@
import { Button } from 'app/atoms/button/button';
import { DownloadIcon } from 'app/atoms/icons-agnostic';
import {
FileTypeArchiveIcon,
FileTypeDocumentIcon,
FileTypePdfIcon,
FileTypeSlidesIcon,
FileTypeSpreadsheetIcon,
FileTypeUnknownIcon,
} from 'app/atoms/icons-colored';
import { Base, Info } from 'app/atoms/text';
import { channelAttachmentListState } from 'app/features/channels/state/channel-attachment-list';
import fileUploadApiClient from 'app/features/files/api/file-upload-api-client';
import fileUploadService from 'app/features/files/services/file-upload-service';
import { formatDate } from 'app/features/global/utils/format-date';
import { formatSize } from 'app/features/global/utils/format-file-size';
import { Message, MessageFileType } from 'app/features/messages/types/message';
import routerService from 'app/features/router/services/router-service';
import { UserType } from 'app/features/users/types/user';
import { useFileViewerModal } from 'app/features/viewer/hooks/use-viewer';
import Media from 'app/molecules/media';
import React from 'react';
import { ArrowRight } from 'react-feather';
import { useRecoilState } from 'recoil';
type FileMessageType = {
message?: Message;
};
type FileUserType = {
user?: UserType;
};
type PropsType = {
file: MessageFileType & FileMessageType & FileUserType;
is_media: boolean;
};
type FilePreviewType = {
file: MessageFileType & FileMessageType & FileUserType;
};
export default ({ file, is_media }: PropsType): React.ReactElement => {
return is_media ? <ChannelMedia file={file} /> : <ChannelFile file={file} />;
};
const ChannelFile = ({ file }: FilePreviewType): React.ReactElement => {
const [, setOpen] = useRecoilState(channelAttachmentListState);
const name = file?.metadata?.name;
const extension = name?.split('.').pop();
const previewUrl = fileUploadApiClient.getFileThumbnailUrlFromMessageFile(file);
const fileType = fileUploadApiClient.mimeToType(file?.metadata?.mime || '');
const { open: openViewer } = useFileViewerModal();
const iconClassName = previewUrl
? 'absolute left-0 top-0 bottom-0 right-0 m-auto w-8 h-8'
: 'absolute bottom-1 left-1 w-6 h-6';
return (
<div
className="flex items-center p-2 hover:bg-zinc-50 rounded-md cursor-pointer"
onClick={() => openViewer(file)}
>
<div className="relative flex bg-zinc-200 rounded-md w-16 h-16 mr-3">
<Media size="md" url={previewUrl} duration={fileType === 'video' ? extension : undefined} />
{(!['image', 'video'].includes(fileType) || !previewUrl) && (
<>
{fileType === 'archive' ? (
<FileTypeArchiveIcon className={iconClassName} />
) : fileType === 'pdf' ? (
<FileTypePdfIcon className={iconClassName} />
) : fileType === 'document' ? (
<FileTypeDocumentIcon className={iconClassName} />
) : fileType === 'slides' ? (
<FileTypeSlidesIcon className={iconClassName} />
) : fileType === 'spreadsheet' ? (
<FileTypeSpreadsheetIcon className={iconClassName} />
) : (
<FileTypeUnknownIcon className={iconClassName} />
)}
</>
)}
</div>
<div className="grow mr-3 overflow-hidden">
<Base className="block whitespace-nowrap overflow-hidden text-ellipsis">{name}</Base>
<Info className="block whitespace-nowrap overflow-hidden text-ellipsis">
{extension?.toLocaleUpperCase()} {formatDate(file?.message?.created_at)} {' '}
{formatSize(file?.metadata?.size)}
</Info>
</div>
<div
className="whitespace-nowrap"
onClick={e => {
e.preventDefault();
e.stopPropagation();
}}
>
<Button
theme="outline"
className="w-9 px-1.5 ml-2 rounded-full border-none"
onClick={() => downloadFile(file)}
>
<DownloadIcon className="w-6 h-6" />
</Button>
{!!file.message && (
<Button
theme="outline"
className="w-9 px-1.5 ml-2 rounded-full border-none"
onClick={() => {
setOpen(false);
gotoMessage(file.message as Message);
}}
>
<ArrowRight className="w-6 h-6" />
</Button>
)}
</div>
</div>
);
};
const ChannelMedia = ({ file }: FilePreviewType): React.ReactElement => {
const previewUrl = fileUploadApiClient.getFileThumbnailUrlFromMessageFile(file);
const type = fileUploadApiClient.mimeToType(file?.metadata?.mime || '');
const { open: openViewer } = useFileViewerModal();
return (
<div
className="cursor-pointer hover:opacity-75 inline-block m-2"
onClick={() => openViewer(file)}
>
<Media
key={file.id}
size="lg"
url={previewUrl}
duration={
type === 'video'
? file?.metadata?.name?.split('.').slice(-1)?.[0]?.toLocaleUpperCase()
: undefined
}
/>
</div>
);
};
const getFileDownloadRoute = (file: MessageFileType): string => {
if (file?.metadata?.source !== 'internal') {
return '';
}
return fileUploadService.getDownloadRoute({
companyId: file.metadata?.external_id?.company_id,
fileId: file.metadata?.external_id?.id,
});
};
const downloadFile = (file: MessageFileType): void => {
const url = getFileDownloadRoute(file);
if (url) {
window.location.href = url;
}
};
const gotoMessage = (message: Message): void => {
routerService.push(
routerService.generateRouteFromState({
companyId: message?.cache?.company_id,
channelId: message?.cache?.channel_id,
threadId: message?.thread_id,
workspaceId: message?.cache?.workspace_id,
...(message.id !== message?.thread_id ? { messageId: message.id } : {}),
}),
);
};
@@ -1,36 +0,0 @@
import { useChannelFileList } from 'app/features/channels/hooks/use-channel-media-files';
import React, { useEffect } from 'react';
import ChannelAttachment from './channel-attachment';
import { LoadingAttachements, NoAttachements } from './commun';
import PerfectScrollbar from 'react-perfect-scrollbar';
type PropsType = {
maxItems?: number;
};
export default ({ maxItems }: PropsType): React.ReactElement => {
const { loading, result, loadMore, loadItems } = useChannelFileList();
useEffect(() => {
loadItems();
}, []);
return (
<PerfectScrollbar
className="-mb-4 py-3 overflow-hidden -mx-2 px-2"
style={{ maxHeight: 'calc(80vh - 100px)', minHeight: 'calc(80vh - 100px)' }}
options={{ suppressScrollX: true, suppressScrollY: false }}
component="div"
onYReachEnd={() => loadMore()}
>
{result.length === 0 && !loading && <NoAttachements />}
{loading ? (
<LoadingAttachements />
) : (
result
.slice(0, maxItems || result.length)
.map(file => <ChannelAttachment key={file.id} file={file} is_media={false} />)
)}
</PerfectScrollbar>
);
};
@@ -1,44 +0,0 @@
import { useChannelMediaList } from 'app/features/channels/hooks/use-channel-media-files';
import fileUploadApiClient from 'app/features/files/api/file-upload-api-client';
import React, { useEffect } from 'react';
import ChannelAttachment from './channel-attachment';
import { LoadingAttachements, NoAttachements } from './commun';
import PerfectScrollbar from 'react-perfect-scrollbar';
type PropsType = {
maxItems?: number;
};
export default ({ maxItems }: PropsType): React.ReactElement => {
const { loading, result, loadMore, loadItems } = useChannelMediaList();
useEffect(() => {
loadItems();
}, []);
return (
<>
<PerfectScrollbar
className="-mb-4 py-3 overflow-hidden -mx-2 px-2"
style={{ maxHeight: 'calc(80vh - 100px)', minHeight: 'calc(80vh - 100px)' }}
options={{ suppressScrollX: true, suppressScrollY: false }}
component="div"
onYReachEnd={() => loadMore()}
>
{result.length === 0 && !loading && <NoAttachements />}
{loading ? (
<LoadingAttachements />
) : (
result
.slice(0, maxItems || result.length)
.map(file => {
const url = fileUploadApiClient.getFileThumbnailUrlFromMessageFile(file);
return url && <ChannelAttachment key={file.id} file={file} is_media={true} />;
})
.filter(Boolean)
)}
</PerfectScrollbar>
</>
);
};
@@ -1,22 +0,0 @@
import { Loader } from 'app/atoms/loader';
import { Info } from 'app/atoms/text';
import Languages from 'app/features/global/services/languages-service';
import React from 'react';
export const LoadingAttachements = (): React.ReactElement => {
return (
<div className="flex h-full justify-center items-center px-2">
<Loader className="h-8 w-8 m-auto" />
</div>
);
};
export const NoAttachements = (): React.ReactElement => {
return (
<div className="flex items-center justify-center flex-col h-64 px-2">
<Info className="p-2">
{Languages.t('components.channel_attachement_list.nothing_found')}
</Info>
</div>
);
};
@@ -1,178 +0,0 @@
import { InformationCircleIcon } from '@heroicons/react/outline';
import { SearchIcon } from '@heroicons/react/solid';
import { Alert } from 'app/atoms/alert';
import { InputDecorationIcon } from 'app/atoms/input/input-decoration-icon';
import { Input } from 'app/atoms/input/input-text';
import Text, { Info } from 'app/atoms/text';
import { useSearchChannelMembersAll } from 'app/features/channel-members-search/hooks/use-search-all';
import Languages from 'app/features/global/services/languages-service';
import { delayRequest } from 'app/features/global/utils/managedSearchRequest';
import Strings from 'app/features/global/utils/strings';
import { useInvitationUsers } from 'app/features/invitation/hooks/use-invitation-users';
import { invitationState } from 'app/features/invitation/state/invitation';
import useRouterChannel from 'app/features/router/hooks/use-router-channel';
import { useEffect, useState } from 'react';
import PerfectScrollbar from 'react-perfect-scrollbar';
import { useRecoilState } from 'recoil';
import { EmailItem } from './email-item';
import { MemberItem } from './member-item';
import { UserItem } from './user-item';
import AccessRightsService from 'app/features/workspace-members/services/workspace-members-access-rights-service';
import { useCurrentWorkspace } from 'app/features/workspaces/hooks/use-workspaces';
export const ChannelMembersListModal = (): JSX.Element => {
const channelId = useRouterChannel();
const [query, setQuery] = useState<string>('');
const { addEmailSuggestion, pendingEmailList, channelMembersList, usersList, search, refresh } =
useSearchChannelMembersAll({
channelId,
});
useEffect(() => {
delayRequest('ChannelMembersListModal', async () => {
search(query);
});
}, [search, query]);
useEffect(() => {
if (channelId) refresh();
}, [channelId]);
return (
<div
className="flex flex-col max-w-full space-y-1"
style={{ height: '80vh', maxHeight: '400px' }}
>
<div>
<InputDecorationIcon
className="mb-2"
suffix={SearchIcon}
input={({ className }) => (
<Input
className={className}
placeholder={Languages.t('scenes.client.channelbar.channelmemberslist.search_invite')}
onChange={e => {
setQuery(e.target.value);
}}
value={query}
/>
)}
/>
{pendingEmailList?.length === 0 && channelMembersList?.length <= 1 && (
<Alert
className="mb-0"
theme="primary"
title={Languages.t('scenes.client.channelbar.channelmemberslist.search_invite_notice')}
icon={InformationCircleIcon}
/>
)}
</div>
<div className="-mx-3">
<PerfectScrollbar
options={{ suppressScrollX: true, suppressScrollY: false }}
component="div"
style={{ width: '100%', height: '100%' }}
>
<div className="mx-1">
{addEmailSuggestion &&
pendingEmailList?.length === 0 &&
channelMembersList?.length === 0 &&
usersList?.length === 0 &&
!Strings.verifyMail(query) && (
<>
<Info className="px-2 mt-2 block">
{Languages.t(
'scenes.client.channelbar.channelmemberslist.search_invite_type_email',
)}
</Info>
</>
)}
{addEmailSuggestion && <EmailSuggestion email={query} />}
{pendingEmailList?.length > 0 && (
<>
<Info className="px-2 mt-2 block">
{Languages.t('scenes.client.channelbar.channelmemberslist.pending_section')}
</Info>
</>
)}
{pendingEmailList &&
pendingEmailList.map((item, index) => {
return (
<div key={`key_${index}`} className="flex py-1 hover:bg-zinc-50 rounded-sm px-2">
<EmailItem email={item} />
</div>
);
})}
{channelMembersList?.length > 0 && (
<>
<Info className="px-2 mt-2 block">
{Languages.t('scenes.client.channelbar.channelmemberslist.members_section')}
</Info>
</>
)}
{channelMembersList &&
channelMembersList
.filter(a => a && a?.user)
.map(cMember => {
return (
<div
key={cMember.user_id}
className="flex py-1 hover:bg-zinc-50 rounded-sm px-2"
>
<MemberItem userId={cMember.user_id} member={cMember} />
</div>
);
})}
{usersList?.length > 0 && (
<>
<Info className="px-2 mt-2 block">
{Languages.t('scenes.client.channelbar.channelmemberslist.not_members_section')}
</Info>
</>
)}
{usersList &&
usersList.map(user => {
return (
<div key={user.id} className="flex py-1 hover:bg-zinc-50 rounded-sm px-2">
<UserItem userId={user.id || ''} />
</div>
);
})}
</div>
</PerfectScrollbar>
</div>
</div>
);
};
const EmailSuggestion = ({ email }: { email: string }) => {
const [, setInvitationOpen] = useRecoilState(invitationState);
const { addInvitation, allowed_guests, allowed_members } = useInvitationUsers();
const { workspace } = useCurrentWorkspace();
const invite = () => {
addInvitation(email);
setInvitationOpen(true);
};
if (!email || !Strings.verifyMail(email)) {
return <></>;
}
return AccessRightsService.hasLevel(workspace?.id, 'moderator') &&
(allowed_guests > 0 || allowed_members > 0) ? (
<div>
<Info className="px-2 mt-2 mb-2 block items-center flex">
<Text type="base" className="cursor-pointer" onClick={() => invite()}>
{Languages.t(
'scenes.client.channelbar.channelmemberslist.invite_to_workspace',
[email],
`Invite ${email} to the workspace ➡`,
)}
</Text>
</Info>
</div>
) : (
<></>
);
};
@@ -1,106 +0,0 @@
import { MailIcon } from '@heroicons/react/outline';
import { XIcon } from '@heroicons/react/solid';
import { Tooltip } from 'antd';
import { Button } from 'app/atoms/button/button';
import { Modal, ModalContent } from 'app/atoms/modal';
import { Info } from 'app/atoms/text';
import { usePendingEmail } from 'app/features/channel-members-search/hooks/use-pending-email-hook';
import Languages from 'app/features/global/services/languages-service';
import { PendingEmail } from 'app/features/pending-emails/types/pending-email';
import useRouterCompany from 'app/features/router/hooks/use-router-company';
import useRouterWorkspace from 'app/features/router/hooks/use-router-workspace';
import WorkspaceUserAPIClient from 'app/features/workspace-members/api/workspace-members-api-client';
import { WorkspacePendingUserType } from 'app/features/workspaces/types/workspace';
import { useState } from 'react';
type IProps = {
email: PendingEmail;
};
export const EmailItem = (props: IProps): JSX.Element => {
const { email } = props.email;
const { loading, cancelInvite } = usePendingEmail(email);
const workspaceId = useRouterWorkspace();
const companyId = useRouterCompany();
const [cancelWorkspaceInvitation, setCancelWorkspaceInvitation] =
useState<WorkspacePendingUserType | null>(null);
const _cancelInvite = async () => {
//Also check if the user is pending in the workspace and display something if that's the case
const pending = await WorkspaceUserAPIClient.listPending(companyId, workspaceId);
const foundWorkspacePending = pending.find(
p => p.email.toLocaleLowerCase() === email.toLocaleLowerCase(),
);
if (foundWorkspacePending) {
//Propose to also cancel workspace invitation
setCancelWorkspaceInvitation(foundWorkspacePending);
} else {
await cancelInvite();
}
};
return (
<>
<Modal open={!!cancelWorkspaceInvitation} onClose={() => setCancelWorkspaceInvitation(null)}>
<ModalContent
theme="warning"
title="Remove workspace invitation ?"
text="Do you want to cancel also the workspace invitation?"
buttons={[
<Button
key="no"
theme="default"
onClick={() => {
setCancelWorkspaceInvitation(null);
cancelInvite();
}}
>
{Languages.t('general.no')}
</Button>,
<Button
className="mr-2"
key="yes"
theme="primary"
onClick={() => {
setCancelWorkspaceInvitation(null);
cancelInvite();
WorkspaceUserAPIClient.cancelPending(companyId, workspaceId, email);
}}
>
{Languages.t('general.yes')}
</Button>,
]}
></ModalContent>
</Modal>
<div className="flex grow items-center space-x-1">
<MailIcon className="h-6 w-6 text-zinc-500" />
<div>
<span className="pl-2">
{email}{' '}
<Info>
(
{Languages.t(
'scenes.client.channels_bar.modals.parts.channel_member_row.label.pending_email',
)}
)
</Info>
</span>
</div>
</div>
<div>
<Tooltip
placement="top"
title={Languages.t('scenes.app.popup.workspaceparameter.pages.cancel_invitation')}
>
<Button
theme="default"
size="sm"
icon={XIcon}
loading={loading}
onClick={() => _cancelInvite()}
></Button>
</Tooltip>
</div>
</>
);
};
@@ -1,81 +0,0 @@
import Avatar from 'app/atoms/avatar';
import { ChannelMemberWithUser } from 'app/features/channel-members-search/types/channel-members';
import Languages from 'app/features/global/services/languages-service';
import UsersService from 'app/features/users/services/current-user-service';
import useRouterCompany from 'app/features/router/hooks/use-router-company';
import useRouterWorkspace from 'app/features/router/hooks/use-router-workspace';
import useRouterChannel from 'app/features/router/hooks/use-router-channel';
import { useChannelMember } from 'app/features/channel-members-search/hooks/member-hook';
import { LogoutIcon } from '@heroicons/react/outline';
import { Tooltip } from 'antd';
import { ButtonConfirm } from 'app/atoms/button/confirm';
import MemberGrade from 'app/views/client/popup/WorkspaceParameter/Pages/WorkspacePartnerTabs/MemberGrade';
type IMemberProps = {
member: ChannelMemberWithUser;
userId?: string;
};
export const MemberItem = (props: IMemberProps): JSX.Element => {
const { member, userId } = props;
const { first_name, email } = member.user;
const companyId = useRouterCompany();
const workspaceId = useRouterWorkspace();
const channelId = useRouterChannel();
const parameters = { companyId, workspaceId, channelId };
const { leave, loading } = useChannelMember(userId || '', parameters);
const isCurrentUser = (): boolean => {
const currentUserId: string = UsersService.getCurrentUserId();
return props.userId === currentUserId;
};
return (
<>
<div className="w-8 flex items-center ">
<Avatar className="" size="xs" avatar={UsersService.getThumbnail(member.user)} />
</div>
<div className="grow flex items-center overflow-hidden ">
<div className="whitespace-nowrap overflow-hidden text-ellipsis">
<span className="font-bold">{first_name}</span>
<span className="pl-2">{email}</span>
</div>
</div>
<div className="mr-2 flex items-center">
<MemberGrade
companyRole={member.user.companies?.find(c => c.company.id === companyId)?.role || ''}
workspaceRole={member.user.workspaces?.find(c => c.id === workspaceId)?.role || ''}
/>
</div>
<div>
{isCurrentUser() ? (
<Tooltip placement="top" title={Languages.t('scenes.app.channelsbar.channel_leaving')}>
<ButtonConfirm
theme="danger"
size="sm"
icon={LogoutIcon}
loading={loading}
onClick={() => leave(props.userId || '')}
/>
</Tooltip>
) : (
<Tooltip
placement="top"
title={Languages.t('scenes.client.channelbar.channelmemberslist.menu.option_2')}
>
<ButtonConfirm
theme="primary"
size="sm"
icon={LogoutIcon}
loading={loading}
onClick={() => leave(props.userId || '')}
/>
</Tooltip>
)}
</div>
</>
);
};
@@ -1,100 +0,0 @@
import Avatar from 'app/atoms/avatar';
import UsersService from 'app/features/users/services/current-user-service';
import { useUser } from 'app/features/users/hooks/use-user';
import { Button } from 'app/atoms/button/button';
import Languages from 'app/features/global/services/languages-service';
import { useChannelMember } from 'app/features/channel-members-search/hooks/member-hook';
import { PlusIcon } from '@heroicons/react/solid';
import useRouterWorkspace from 'app/features/router/hooks/use-router-workspace';
import React from 'react';
import { Modal, ModalContent } from 'app/atoms/modal';
import useRouterCompany from 'app/features/router/hooks/use-router-company';
import ConsoleService from 'app/features/console/services/console-service';
import MemberGrade from 'app/views/client/popup/WorkspaceParameter/Pages/WorkspacePartnerTabs/MemberGrade';
type IUserProps = {
userId: string;
};
export const UserItem = (props: IUserProps): JSX.Element => {
const { userId } = props;
const user = useUser(userId || '');
const workspaceId = useRouterWorkspace();
const companyId = useRouterCompany();
const [confirmWorkspaceInvitation, setConfirmWorkspaceInvitation] =
React.useState<boolean>(false);
if (!user) {
return <></>;
}
const { addMember, loading } = useChannelMember(userId || '');
const [full_name, avatar] = [UsersService.getFullName(user), UsersService.getThumbnail(user)];
const _addMember = () => {
if (!(user.workspaces || []).map(w => w.id).includes(workspaceId)) {
//Ask for confirmation to invite the user to the workspace first
setConfirmWorkspaceInvitation(true);
} else {
addMember(userId || '');
}
};
return (
<>
<Modal open={confirmWorkspaceInvitation} onClose={() => setConfirmWorkspaceInvitation(false)}>
<ModalContent
theme="warning"
title="Also invite to the workspace?"
text="This user is not in the current workspace, he will be invited."
buttons={[
<Button key="no" theme="default" onClick={() => setConfirmWorkspaceInvitation(false)}>
{Languages.t('general.no')}
</Button>,
<Button
className="mr-2"
key="yes"
theme="primary"
onClick={async () => {
setConfirmWorkspaceInvitation(false);
await ConsoleService.addMailsInWorkspace({
workspace_id: workspaceId || '',
company_id: companyId || '',
emails: [user.email],
});
await addMember(userId || '');
}}
>
{Languages.t('general.yes')}
</Button>,
]}
></ModalContent>
</Modal>
<div className="w-8 flex items-center ">
<Avatar size="xs" avatar={avatar} />
</div>
<div className="grow flex items-center overflow-hidden ">
<div className="whitespace-nowrap overflow-hidden text-ellipsis">
<span className="font-bold">{full_name}</span>
<span className="pl-2">{user.email}</span>
</div>
</div>
<div className="mr-2 flex items-center">
<MemberGrade
companyRole={user.companies?.find(c => c.company.id === companyId)?.role || ''}
workspaceRole={user.workspaces?.find(c => c.id === workspaceId)?.role || ''}
/>
</div>
<div>
<Button
theme="primary"
size="sm"
icon={PlusIcon}
loading={loading}
onClick={() => _addMember()}
/>
</div>
</>
);
};
@@ -1,19 +0,0 @@
import { Modal, ModalContent } from 'app/atoms/modal';
import { useUsersSearchModal } from 'app/features/channel-members-search/state/search-channel-member';
import Languages from 'app/features/global/services/languages-service';
import { ChannelMembersListModal } from './channel-members-modal';
export default () => {
const { open, setOpen } = useUsersSearchModal();
return (
<Modal open={open} onClose={() => setOpen(false)} className="sm:w-[80vw] sm:max-w-xl">
<ModalContent
textCenter
title={Languages.t('scenes.apps.parameters.workspace_sections.members')}
>
<ChannelMembersListModal />
</ModalContent>
</Modal>
);
};
@@ -1,164 +0,0 @@
import { SearchIcon } from '@heroicons/react/outline';
import Avatar from 'app/atoms/avatar';
import { InputDecorationIcon } from 'app/atoms/input/input-decoration-icon';
import { Input } from 'app/atoms/input/input-text';
import { Loader } from 'app/atoms/loader';
import { Modal, ModalContent } from 'app/atoms/modal';
import { Base, Info } from 'app/atoms/text';
import {
useSearchChannels,
useSearchChannelsLoading,
} from 'app/features/search/hooks/use-search-channels';
import { SearchInputState } from 'app/features/search/state/search-input';
import Block from 'app/molecules/grouped-rows/base';
import { useEffect, useState } from 'react';
import PerfectScrollbar from 'react-perfect-scrollbar';
import { atom, useRecoilState, useSetRecoilState } from 'recoil';
import Button from '../buttons/button';
import Emojione from '../emojione/emojione';
import UsersService from 'app/features/users/services/current-user-service';
import { ChannelType } from 'app/features/channels/types/channel';
import { Checkbox } from 'app/atoms/input/input-checkbox';
import Languages from '../../features/global/services/languages-service';
import Icon from '../icon/icon';
export const SelectChannelModalAtom = atom<boolean>({
key: 'SelectChannelModalAtom',
default: false,
});
export const ChannelSelectorModal = (props: {
initialChannels: ChannelType[];
onChange: (channels: ChannelType[]) => void;
lockDefaultChannels?: boolean;
}) => {
const [open, setOpen] = useRecoilState(SelectChannelModalAtom);
const [channels, setChannels] = useState<ChannelType[]>([]);
return (
<Modal open={open} onClose={() => setOpen(false)}>
<ModalContent title={Languages.t('components.channelselector.title')}>
<ChannelSelector
initialChannels={props.initialChannels}
onChange={channels => {
setChannels(channels);
}}
lockDefaultChannels={props.lockDefaultChannels}
/>
<Button
className="w-full mt-2 text-center justify-center"
disabled={channels.length === 0}
onClick={() => {
props.onChange(channels);
}}
>
{Languages.t('components.channelselector.confirm')}
</Button>
</ModalContent>
</Modal>
);
};
export const ChannelSelector = (props: {
initialChannels: ChannelType[];
onChange: (channels: ChannelType[]) => void;
lockDefaultChannels?: boolean;
}) => {
const [selectedChannels, setSelectedChannels] = useState<ChannelType[]>(props.initialChannels);
const setSearch = useSetRecoilState(SearchInputState);
const { channels } = useSearchChannels();
const loading = useSearchChannelsLoading();
const displayedChannels = props.lockDefaultChannels
? [
...channels.filter(channel => channel.is_default),
...channels.filter(channel => !channel.is_default),
]
: channels;
useEffect(() => {
props.onChange(selectedChannels);
}, [selectedChannels]);
return (
<>
<InputDecorationIcon
prefix={
loading
? ({ className }) => (
<div className={className + ' !h-6'}>
<Loader className="h-4 w-4" />
</div>
)
: SearchIcon
}
input={({ className }) => (
<Input
className={className}
placeholder={Languages.t('components.channelselector.search')}
onChange={e => setSearch({ query: e.target.value })}
/>
)}
/>
<PerfectScrollbar
className="border-b border-zinc-200 py-3 overflow-hidden"
style={{ maxHeight: '40vh', minHeight: '40vh' }}
options={{ suppressScrollX: true, suppressScrollY: false }}
component="div"
>
{displayedChannels.map(channel => {
const name =
channel.name || channel.users?.map(u => UsersService.getFullName(u)).join(', ');
return (
<Block
key={channel.id}
avatar={
<Avatar
icon={
channel.visibility === 'direct' ? undefined : (
<div className="w-full h-full flex justify-center items-center">
<Emojione type={channel.icon || ''} />
</div>
)
}
title={name || ''}
/>
}
title={<Base className="capitalize">{name}</Base>}
subtitle={
<Info className="-mt-2">{`${
channel.stats?.members || channel.members?.length || 0
} members`}</Info>
}
suffix={
<div className="flex text-center pr-4">
{props.lockDefaultChannels &&
props.initialChannels.find(({ id }) => channel.id === id) &&
channel.is_default ? (
<div className="font-medium h-6 flex items-center justify-center text-sm rounded-full text-white bg-blue-500">
<Icon type="lock-alt" className="m-icon-small" />
</div>
) : (
<Checkbox
value={!!selectedChannels.find(({ id }) => channel.id === id)}
onChange={() => {
if (selectedChannels.includes(channel)) {
setSelectedChannels(selectedChannels.filter(c => c.id !== channel.id));
} else if (channel.id) {
setSelectedChannels([...selectedChannels, channel]);
}
}}
/>
)}
</div>
}
className="py-2"
/>
);
})}
</PerfectScrollbar>
</>
);
};
@@ -1,11 +0,0 @@
.companyMessagesCounter {
margin-right: 8px;
.info {
font-weight: 400;
opacity: 0.5;
}
.link {
margin-top: 4px;
}
}
@@ -1,71 +0,0 @@
import React, { useEffect, useState } from 'react';
import { Col, Progress, Row, Typography } from 'antd';
import './company-messages-counter.scss';
import i18n from 'i18next';
import Languages from 'app/features/global/services/languages-service';
import { useCurrentCompany } from 'app/features/companies/hooks/use-companies';
import FeatureTogglesService, {
FeatureNames,
} from 'app/features/global/services/feature-toggles-service';
import MessageHistoryService from 'app/features/messages/services/message-history-service';
import consoleService from 'app/features/console/services/console-service';
const { Text, Title, Link } = Typography;
const CompanyMessagesCounter = () => {
const [messagesCount, setMessagesCount] = useState<number>(1);
const companyMessagesLimit = MessageHistoryService.getLimitCompanyMessages();
const { company } = useCurrentCompany();
const companySubscriptionUrl = consoleService.getCompanySubscriptionUrl(company.id);
const onClickLink = () => window.open(companySubscriptionUrl, 'blank');
useEffect(() => {
if (company) {
setMessagesCount(company.stats?.total_messages || 1);
}
}, [company]);
return !FeatureTogglesService.isActiveFeatureName(FeatureNames.MESSAGE_HISTORY) ? (
<>
<Row
justify="space-around"
align="middle"
wrap={false}
style={{
padding: '0px 0px',
width: '100%',
}}
>
<Col className="small-left-margin companyMessagesCounter" style={{ lineHeight: '16px' }}>
<Title level={4}>
{Languages.t('scenes.app.channelsbar.currentuser.company_messages_counter_header', [])}
</Title>
<Text className="info">
{Languages.t('scenes.app.channelsbar.currentuser.company_messages_counter_info', [
Intl.NumberFormat(i18n.language).format(companyMessagesLimit),
])}
</Text>
<div className="link">
<Link onClick={onClickLink}>
{Languages.t('scenes.app.channelsbar.currentuser.company_messages_counter_link', [])}
</Link>
</div>
</Col>
<Col>
<Progress
type="circle"
format={() => (messagesCount / 1000).toFixed(1) + 'k'}
percent={(messagesCount / companyMessagesLimit) * 100}
width={55}
/>
</Col>
</Row>
</>
) : (
<></>
);
};
export default CompanyMessagesCounter;
@@ -1,59 +0,0 @@
/* eslint-disable react/jsx-key */
import React, { Component } from 'react';
import InputWithClipBoard from 'components/input-with-clip-board/input-with-clip-board.jsx';
import './component-doc.scss';
export default class ComponentDoc extends React.Component {
constructor() {
super();
}
render() {
return (
<div className="component_doc">
<div className="section header">
{this.props.title && <div className="title">{this.props.title}</div>}
{this.props.import && (
<InputWithClipBoard value={this.props.import} className="full_width" />
)}
</div>
<div className="section">{this.props.children}</div>
<div className="section">
{this.props.properties && [
<div className="subtitle">Properties</div>,
<table className="properties">
<thead>
<tr>
<td>Property</td>
<td>Type</td>
<td>Default</td>
<td>Description</td>
</tr>
</thead>
{this.props.properties.map(property => {
return (
<tr>
<td>{property[0]}</td>
<td>
{property[1].split(',').map(item => (
<span className="type">{item}</span>
))}
</td>
<td>{property[2]}</td>
<td>{property[3]}</td>
</tr>
);
})}
</table>,
<br />,
<br />,
]}
{this.props.infos && [
<div className="subtitle">Infos</div>,
<div className="text">{this.props.infos}</div>,
]}
</div>
</div>
);
}
}
@@ -1,50 +0,0 @@
.component_doc {
background: var(--grey-background);
padding: 25px;
max-height: 100vh;
overflow: scroll;
box-sizing: border-box;
.section {
background: var(--white);
max-width: 900px;
margin: auto;
left: 0;
right: 0;
padding: 25px;
box-sizing: border-box;
border-radius: var(--border-radius-base);
margin-top: 25px;
}
.header {
input {
background: var(--black);
color: #fff;
font-family: monospace;
}
}
.subtitle {
font-weight: 700;
}
.properties {
border-collapse: collapse;
width: 100%;
thead {
font-weight: 700;
}
td {
border: 1px solid var(--grey-background);
padding: 8px;
}
.type {
background: var(--grey-background);
padding: 2px 4px;
border-radius: var(--border-radius-base);
}
}
}
@@ -1,370 +0,0 @@
/* eslint-disable react/jsx-key */
import React, { Component } from 'react';
import Emojione from 'components/emojione/emojione';
import Icon from 'app/components/icon/icon.jsx';
import Menu from 'components/menus/menu.jsx';
import MenusBodyLayer from 'app/components/menus/menus-body-layer.jsx';
import AutoComplete from 'components/auto-complete/auto-complete';
import UserPicker from 'components/user-picker/user-picker.jsx';
import EmojiPicker from 'components/emoji-picker/emoji-picker.jsx';
import Directory from 'components/drive/directory.jsx';
import File from 'components/drive/file';
import DriveMultiSelector from 'components/drive/drive-multi-selector.jsx';
import Rounded from 'components/inputs/rounded.jsx';
import DraggableBodyLayer from 'components/draggable/draggable-body-layer.jsx';
import emojiService from 'app/components/rich-text-editor/emojis-service.jsx';
import User from 'components/ui/user.jsx';
import AutoHeight from 'components/auto-height/auto-height.jsx';
import Tooltip from 'components/tooltip/tooltip.jsx';
import InputWithClipBoard from 'components/input-with-clip-board/input-with-clip-board.jsx';
import './components-tester.scss';
import GroupInputs from './group/inputs.js';
export default class ComponentsTester extends React.Component {
constructor() {
super();
this.state = {
autocompValue: '',
visible: false,
};
}
render() {
return [
<div className="componentTester">
<div className="section">
<div className="title">Test des composants</div>
<div className="subtitle">ctrl+alt+e pour revenir à Tdrive</div>
</div>
<GroupInputs />
<div className="section">
<div className="title">Icones</div>
<div className="title">
Title <Icon type="comment" />
</div>
<div className="subtitle">
Sub title <Icon type="search" />
</div>
<div className="text">
Text <Icon type="grid" />
</div>
</div>
<div className="section">
<div className="title">Emojione</div>
<div className="title">
Title <Emojione type="🍺" />
</div>
<div className="subtitle">
Subtitle <Emojione type=":smile:" />
</div>
<div className="text">
Text <Emojione type="👍" />
</div>
</div>
<div className="section">
<div className="title">Tooltip</div>
<div className="text">
<Tooltip
ref={o => (this.tooltip = o)}
tooltip={'toolltiiiiiiiiip'}
position={'top'}
overable={false}
>
<div className="to">this is my tooltip top</div>
</Tooltip>
</div>
<button onClick={() => this.tooltip.openWithTimeOut(2)}>open</button>
<div className="text">
<Tooltip tooltip={'toolltiiiiiiiiip'} position={'bottom'}>
<div className="to">this is my tooltip top</div>
</Tooltip>
</div>
</div>
<div className="section">
<div className="title">Input with clipboard</div>
<div className="text">
<InputWithClipBoard
value={
'https://join.slack.com/t/yopmail-groupe/shared_invite/enQtNTY2NjEwNTM4MTMxLTFmYTg2N2U1NjZiMDg4NmMxM2UyOTQ5YjQ1YWE4ZGE2NGVkNTRhMjRlZGMwMmMxZjk1YjViN2M5Y2MzMTgxYjQ'
}
disabled={true}
/>
</div>
</div>
<div className="section">
<div className="title">Menus</div>
<Menu
menu={[
{ type: 'menu', text: 'Un menu', icon: 'list' },
{
type: 'menu',
text: 'Un menu avec sous menu',
icon: 'grid',
submenu: [
{ text: 'Sub menu 4', icon: 'search' },
{ text: 'Sub menu 5', icon: 'search' },
{ text: 'Sub menu 6', icon: 'search' },
],
},
{
type: 'menu',
text: 'Un menu avec sous menu',
icon: 'grid',
submenu: [
{
type: 'react-element',
text: 'Sub menu 6',
icon: 'search',
reactElement: <div className="hello">salut</div>,
},
],
},
{ type: 'separator' },
{ type: 'text', text: 'Du texte' },
]}
>
Click here !
</Menu>
</div>
<div className="section">
<div className="title">Auto complete one line</div>
<AutoComplete
search={[
(text, cb) => {
cb(
[
{
id: 1,
username: 'benoit',
mail: 'benoit.tallandier@g.a',
userimage: 'https://randomuser.me/api/portraits/men/40.jpg',
},
{
id: 2,
username: 'romaric',
mail: 'romaric.mourgues@g.a',
userimage: 'https://randomuser.me/api/portraits/men/41.jpg',
},
{
id: 3,
username: 'guillaume',
mail: 'romaric.mourgues@g.a',
userimage: 'https://randomuser.me/api/portraits/men/42.jpg',
},
{
id: 4,
username: 'Amandine',
mail: 'romaric.mourgues@g.a',
userimage: 'https://randomuser.me/api/portraits/women/40.jpg',
},
].filter(function (item) {
if (item && item.username.indexOf(text) !== -1) {
return true;
}
return false;
}),
);
},
(text, cb) => {
cb(emojiService.search(text));
},
]}
max={[5, 5]}
renderItemChoosen={[
item => {
return '@' + (item || {}).username + ' ';
},
item => {
return item.shortname + ' ';
},
]}
renderItem={[
item => {
return (
<div style={{ margin: '3px 0px' }}>
<User data={item} />
</div>
);
},
item => {
return (
<div style={{ margin: '1px 0px' }}>
<Emojione type={item.shortname} /> {item.shortname}
</div>
);
},
]}
regexHooked={[/\B@([\-+\w]*)$/, /\B:([\-+\w]{0}[\-+\w]*)$/]}
placeholder="Placeholder"
autoHeight
/>
</div>
<div className="section">
<div className="title">Multi-line input</div>
<AutoHeight />
</div>
<div className="section">
<div className="title">Users picker</div>
<div className="subtitle">In menu</div>
<Menu
menu={[
{
type: 'react-element',
reactElement: (
<UserPicker
value={this.state.users_picker_value}
onChange={list => this.setState({ users_picker_value: list })}
/>
),
},
]}
>
<div className="text">Open menu</div>
</Menu>
<br />
<br />
<div className="subtitle">Inline</div>
<UserPicker
value={this.state.users_picker_value}
onChange={list => this.setState({ users_picker_value: list })}
inline
/>
<br />
<div className="subtitle">Inline read only</div>
<UserPicker value={this.state.users_picker_value} readOnly />
<br />
<div className="subtitle">Inline read only miniatures</div>
<UserPicker value={this.state.users_picker_value} readOnly mini />
</div>
<div className="section">
<div className="title">Emoji picker</div>
<Menu menu={[{ type: 'react-element', reactElement: <EmojiPicker /> }]}>
Click here !
</Menu>
</div>
<div className={'section ' + (this.state.drive_list ? 'list' : 'grid')}>
<button
style={{ float: 'right' }}
className="medium"
onClick={() => {
this.setState({ drive_list: !this.state.drive_list });
}}
>
Toggle display mode
</button>
<div className="title">Drive</div>
<div className="text">
TODO: Tester drag drop depuis ordinateur
<br />
TODO: Faire apparaitre l'icone "drag + menu" au survol
</div>
<DriveMultiSelector
scroller={this.drive_scroller}
selectionType="drive_1"
style={{ maxHeight: 400 }}
>
<div style={{ padding: 5 }}>
<div className="subtitle">Dossiers</div>
<Directory
selectionType="drive_1"
data={{ id: 1, name: 'Drive', type: 'google_drive' }}
/>
<Directory selectionType="drive_1" data={{ id: 2, name: 'Accepté', size: '6000' }} />
<Directory selectionType="drive_1" data={{ id: 3, name: 'Refusés', size: '19000' }} />
<Menu
menu={[
{ type: 'menu', text: 'Nouveau dossier', icon: 'plus' },
{ type: 'menu', text: 'ChannelServiceImpl externe', icon: 'grid' },
]}
>
<Rounded />
</Menu>
<br />
<br />
<div className="subtitle">Fichiers</div>
<File
selectionType="drive_1"
data={{
id: 4,
name: 'fonds_marins.jpeg',
size: '3000',
preview_url:
'https://cdn12.picryl.com/photo/2016/12/31/wintry-winter-mood-snow-landscape-nature-landscapes-10fa27-1024.jpg',
}}
/>
<File
selectionType="drive_1"
data={{
id: 5,
name: 'photo.jpg',
size: '2000',
preview_url:
'https://www.goodfreephotos.com/albums/turkey/other/landscape-with-river-valley-in-turkey.jpg',
}}
/>
<File
selectionType="drive_1"
data={{
id: 6,
name: 'montagnes.png',
size: '1500',
preview_url: 'https://c1.staticflickr.com/6/5081/5252291555_830a56721b_b.jpg',
}}
/>
<File
selectionType="drive_1"
data={{
id: 7,
name: 'chapeau.png',
size: '532',
preview_url:
'https://www.lacerisesurlechapeau.com/wp-content/uploads/2016/07/chapeau-original-trendy-feutre.png',
}}
/>
<Menu
menu={[
{ type: 'menu', text: 'Nouveau fichier', icon: 'plus', submenu: [] },
{ type: 'menu', text: 'Depuis un lien', icon: 'link' },
{ type: 'menu', text: "Importer depuis l'ordinateur", icon: 'monitor' },
]}
>
<Rounded />
</Menu>
</div>
</DriveMultiSelector>
</div>
</div>,
<MenusBodyLayer />,
<DraggableBodyLayer />,
];
}
}
@@ -1,19 +0,0 @@
.componentTester {
background: #f5f5f7;
padding: 25px;
max-height: 100vh;
overflow: scroll;
box-sizing: border-box;
.section {
background: #fff;
max-width: 900px;
margin: auto;
left: 0;
right: 0;
padding: 25px;
box-sizing: border-box;
border-radius: var(--border-radius-base);
margin-top: 25px;
}
}
@@ -1,124 +0,0 @@
/* eslint-disable react/jsx-key */
import React, { Component } from 'react';
import Checkbox from 'app/components/inputs/deprecated_checkbox.jsx';
import Switch from 'components/inputs/switch';
import Button from 'components/buttons/button.jsx';
import Input from 'components/inputs/input.jsx';
import StepCounter from 'components/step-counter/step-counter.jsx';
import Languages from 'app/features/global/services/languages-service';
import { lang } from 'moment';
export default class GroupInputs extends React.Component {
constructor() {
super();
this.state = {
input: 'Example',
};
}
render() {
return [
<div className="section">
<div className="title">Formulaires</div>
<div className="subtitle">className="big"</div>
<Input
className="big"
value={this.state.input}
onChange={evt => this.setState({ input: evt.target.value })}
/>
<br />
<Checkbox
className="big"
value={this.state.input}
onChange={value => this.setState({ input: value })}
/>
<br />
<Switch
className="big"
checked={this.state.input}
onChange={value => this.setState({ input: value })}
/>
<br />
<div className="subtitle">className="medium"</div>
<Input
className="medium"
value={this.state.input}
onChange={evt => this.setState({ input: evt.target.value })}
/>
<br />
<Checkbox
className="medium"
checked={this.state.input}
onChange={value => this.setState({ input: value })}
/>
<br />
<Switch
className="medium"
checked={this.state.input}
onChange={value => this.setState({ input: value })}
/>
<br />
<div className="subtitle">className="small"</div>
<Input
className="small"
value={this.state.input}
onChange={evt => this.setState({ input: evt.target.value })}
/>
<br />
<Checkbox
className="small"
value={this.state.input}
onChange={value => this.setState({ input: value })}
/>
<br />
<Switch
className="small"
checked={this.state.input}
onChange={value => this.setState({ input: value })}
/>
<br />
</div>,
<div className="section">
<div className="big_form">
<StepCounter current={3} total={4} />
<div className="title">Create my company 3/4</div>
<div className="subtitle">
{Languages.t(
'scenes.app.workspaces.create_company.importations.title_1',
[],
'Already working with digital tools? Import or integrate your tools now!',
)}
<br />
{Languages.t(
'scenes.app.workspaces.create_company.importations.title_2',
[],
'No worries, you can do this later!',
)}
</div>
<div className="body">
<div className="input_with_label">
<div className="label">What's your company name ?</div>
<Input
className="big full_width"
value={this.state.company_name || ''}
onChange={evt => this.setState({ company_name: evt.target.value })}
placeholder="Ex : Google, Aircall, Doctolib"
/>
</div>
</div>
<div className="footer">
<Button value={Languages.t('general.back', [], 'Back')} inline />
<Button value={Languages.t('general.continue', [], 'Continue')} primary />
</div>
</div>
</div>,
];
}
}
@@ -3,10 +3,10 @@ import './connection-indicator.scss';
import ErrorOutlinedIcon from '@material-ui/icons/ErrorOutlined';
import HourglassEmpty from '@material-ui/icons/HourglassEmpty';
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
import Languages from 'app/features/global/services/languages-service';
import { ConnectedState } from 'app/features/users/state/atoms/connected';
import Languages from '@features/global/services/languages-service';
import { ConnectedState } from '@features/users/state/atoms/connected';
import { useRecoilState } from 'recoil';
import WebSocket, { WebsocketEvents } from 'app/features/global/types/websocket-types';
import WebSocket, { WebsocketEvents } from '@features/global/types/websocket-types';
export default () => {
const [{ connected, reconnecting }, setState] = useRecoilState(ConnectedState);
@@ -6,9 +6,9 @@ import AddIcon from '@material-ui/icons/AddOutlined';
import GearIcon from '@material-ui/icons/BuildOutlined';
import Input from 'components/inputs/input.jsx';
import './connectors-list-manager.scss';
import Languages from 'app/features/global/services/languages-service';
import WorkspacesApps from 'app/deprecated/workspaces/workspaces_apps.jsx';
import { getCompanyApplication as getApplication } from 'app/features/applications/state/company-applications';
import Languages from '@features/global/services/languages-service';
import WorkspacesApps from '@deprecated/workspaces/workspaces_apps.jsx';
import { getCompanyApplication as getApplication } from '@features/applications/state/company-applications';
export default class ConnectorsListManager extends Component {
constructor(props) {
@@ -1,11 +1,11 @@
import { Loader } from 'app/atoms/loader';
import { OpenDesktopPopup } from 'app/components/open-desktop-popup/open-desktop-popup';
import Electron from 'app/features/global/framework/electron-service';
import { useGlobalEffect } from 'app/features/global/hooks/use-global-effect';
import { LoadingState } from 'app/features/global/state/atoms/Loading';
import { useWebState } from 'app/features/global/state/atoms/use-web';
import { getDevice } from 'app/features/global/utils/device';
import { useCurrentUser } from 'app/features/users/hooks/use-current-user';
import { Loader } from '@atoms/loader';
import { OpenDesktopPopup } from '@components/open-desktop-popup/open-desktop-popup';
import Electron from '@features/global/framework/electron-service';
import { useGlobalEffect } from '@features/global/hooks/use-global-effect';
import { LoadingState } from '@features/global/state/atoms/Loading';
import { useWebState } from '@features/global/state/atoms/use-web';
import { getDevice } from '@features/global/utils/device';
import { useCurrentUser } from '@features/users/hooks/use-current-user';
import React from 'react';
import { useRecoilState } from 'recoil';
import { detectDesktopAppPresence } from 'src/utils/browser-detect';
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import InitService from '../../features/global/services/init-service';
import LocalStorage from '../../features/global/framework/local-storage-service';
import DownloadBanner from 'app/molecules/download-banner';
import DownloadBanner from '@molecules/download-banner';
import { detectDesktopAppPresence } from '../../../utils/browser-detect';
export default (): React.ReactElement => {
@@ -1,23 +0,0 @@
import React, { Component } from 'react';
import DraggableManager from './draggable-manager.js';
export default class DraggableBodyLayer extends Component {
constructor() {
super();
this.state = {
draggable_manager: DraggableManager,
};
this.draggable_body_dom = null;
DraggableManager.addListener(this);
}
componentDidMount() {
DraggableManager.registerContainer(this.draggable_body_dom);
}
componentWillUnmount() {
DraggableManager.removeListener(this);
}
render() {
return <div ref={node => (this.draggable_body_dom = node)} />;
}
}
@@ -1,238 +0,0 @@
import React, { Component } from 'react';
import Observable from 'app/deprecated/CollectionsV1/observable.js';
import SelectionsManager from 'app/deprecated/SelectionsManager/SelectionsManager.js';
class DraggableManager extends Observable {
constructor() {
super();
this.setObservableName('draggable_manager');
this.container = null;
this.move = this.move.bind(this);
this.end = this.end.bind(this);
this.start = this.start.bind(this);
this.drag = false;
this.clone_created = false;
this.drag_initial_point = [];
this.current_node_initial_position = [];
}
registerContainer(container) {
this.container = container;
}
setDropCallback(key, callback) {
this.callback_key = key;
this.callback = callback;
}
removeDropCallback(key) {
if (this.callback_key == key) {
this.callback = false;
this.callback_key = null;
}
}
start(evt, node, react_draggable) {
if (this.dragging) {
return;
}
if (react_draggable.props.dragHandler) {
var onHandler = false;
var handlers = node.getElementsByClassName(react_draggable.props.dragHandler);
if (handlers.length > 0) {
Array.from(handlers).forEach(item => {
if (item == evt.target || item.contains(evt.target)) {
onHandler = true;
}
});
}
if (!onHandler) {
return;
}
}
this.data = {};
if (node) {
this.current_node = node;
}
if (react_draggable) {
this.current_react_draggable = react_draggable;
this.props = {
parentClassOnDrag: this.current_react_draggable.props.parentClassOnDrag,
minMove: this.current_react_draggable.props.minMove,
};
this.data = this.current_react_draggable.props.data;
}
this.selected_number = 1;
if (this.data.selection_type) {
this.selected_number = SelectionsManager.selected_per_type[this.data.selection_type];
if (this.selected_number) {
this.selected_number = Object.keys(this.selected_number).length;
}
if (!this.selected_number) {
this.selected_number = 1;
}
}
evt.stopPropagation();
evt.preventDefault();
document.addEventListener('mousemove', this.move);
document.addEventListener('mouseup', this.end);
var rect = window.getBoundingClientRect(this.current_node);
rect.x = rect.x || rect.left;
rect.y = rect.y || rect.top;
this.clone_created = false;
this.drag_initial_point = [evt.clientX, evt.clientY];
this.current_node_initial_position = [rect.left, rect.top, node.clientWidth, node.clientHeight];
this.drag = true;
if (this.current_react_draggable) this.current_react_draggable.setState({ drag: true });
}
end(evt, node, react_draggable) {
if (this.dragging) {
evt.preventDefault();
evt.stopPropagation();
}
this.current_react_draggable && this.current_react_draggable.setState({ hide_original: false });
if (!this.data) {
return;
}
if (node) {
this.current_node = node;
}
if (react_draggable) {
this.current_react_draggable = react_draggable;
this.props = {
parentClassOnDrag: this.current_react_draggable.props.parentClassOnDrag,
minMove: this.current_react_draggable.props.minMove,
};
}
document.removeEventListener('mousemove', this.move);
document.removeEventListener('mouseup', this.end);
document.body.classList.remove('dragging');
document.body.classList.remove('drag_' + this.data.type);
this.drag = false;
this.dragging = false;
if (this.current_react_draggable)
this.current_react_draggable.setState({ drag: false, dragging: false });
var delete_delay = 0;
if (this.callback) {
this.callback(this.data);
} else {
if (this.clone) {
this.clone.style.transition = 'transform 0.2s';
this.clone.style.transform =
'translateX(' +
this.current_node_initial_position[0] +
'px) translateY(' +
this.current_node_initial_position[1] +
'px)';
}
delete_delay = 200;
}
setTimeout(() => {
if (this.clone) {
try {
this.container.removeChild(this.clone);
} catch (e) {}
}
if (this.current_react_draggable)
this.current_react_draggable.setState({ hide_original: false });
if (this.props.parentClassOnDrag) {
this.props.parentClassOnDrag.split(' ').forEach(c => {
this.container.classList.remove(c);
});
}
}, delete_delay);
this.data = {};
}
move(evt) {
if (!this.data) {
return;
}
var deltaX = evt.clientX - this.drag_initial_point[0];
var deltaY = evt.clientY - this.drag_initial_point[1];
if (
!this.clone_created &&
Math.max(Math.abs(deltaX), Math.abs(deltaY)) < (this.props.minMove || 10)
) {
return;
}
if (this.drag) {
if (!this.clone_created) {
var html = this.current_node.outerHTML;
var div = document.createElement('div');
div.classList.add('draggable_clone');
div.innerHTML = html.trim();
div.firstChild.classList.remove('dragging_opacity');
div.firstChild.classList.remove('fade_in');
div.firstChild.classList.remove('is_selected');
div.style.width = this.current_node_initial_position[2] + 'px';
div.style.height = this.current_node_initial_position[3] + 'px';
this.clone = div;
if (this.selected_number > 1) {
var div_number = document.createElement('div');
div_number.classList.add('draggable_number');
div_number.innerHTML = this.selected_number;
this.clone.appendChild(div_number);
}
this.container.appendChild(this.clone);
this.clone_created = true;
if (this.props.parentClassOnDrag) {
this.props.parentClassOnDrag.split(' ').forEach(c => {
this.container.classList.add(c);
});
}
if (
this.current_react_draggable &&
this.current_react_draggable.props &&
this.current_react_draggable.props.onDragStart
) {
this.current_react_draggable.props.onDragStart(evt);
}
this.dragging = true;
if (this.current_react_draggable)
this.current_react_draggable.setState({ dragging: true, hide_original: true });
}
document.body.classList.add('dragging');
document.body.classList.add('drag_' + this.data.type);
this.clone.style.transform =
'translateX(' +
(deltaX + this.current_node_initial_position[0]) +
'px) translateY(' +
(deltaY + this.current_node_initial_position[1]) +
'px)';
}
}
}
const service = new DraggableManager();
export default service;
@@ -1,87 +0,0 @@
.draggable {
.js-drag-handler {
cursor: grab;
width: 100%;
min-height: 16px;
}
}
.dragging {
cursor: grabbing !important;
}
.draggable_clone {
position: absolute;
z-index: 1000;
left: 0px;
top: 0px;
pointer-events: none;
& > * {
opacity: 0.5;
}
.draggable_number {
opacity: 1;
position: absolute;
right: -10px;
bottom: -10px;
background: #fff;
color: var(--primary);
font-size: 16px;
width: 40px;
height: 40px;
text-align: center;
line-height: 40px;
border-radius: 50%;
box-shadow: var(--box-shadow-base);
}
}
.dragging_opacity {
//opacity: 0.5;
}
.droppable_zone {
background: #f2f2f200;
margin: 0px;
border-radius: var(--border-radius-base);
transition: margin 0.2s, border 0.2s;
border: 0px dashed #ffffff00;
.empty {
border-radius: var(--border-radius-base);
background: #ffffff;
padding: 0px;
text-align: center;
color: #00000088;
margin: 0px;
opacity: 0;
overflow: hidden;
max-height: 0px;
transition: margin 0.2s, opacity 0.2s, height 0.2s, padding 0.2s, max-height 0.2s;
}
}
.dragging.drag_channel .droppable_zone.for_channel,
.dragging.drag_message .droppable_zone.for_message {
border: 2px dashed #f2f2f2;
margin: 10px 0px;
.empty {
padding: 10px;
opacity: 1;
max-height: 40px;
.m-icon-small {
vertical-align: bottom;
}
}
&:hover {
border: 2px dashed #eaeaea;
background: #f2f2f2;
.empty {
background: #f2f2f2;
}
}
}
@@ -1,44 +0,0 @@
/* eslint-disable react/prop-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
// eslint-disable-next-line @typescript-eslint/no-use-before-define
import React from 'react';
import './draggable.scss';
import DraggableManager from './draggable-manager.js';
import classNames from 'classnames';
type PropsType = { [key: string]: any };
type StateType = { [key: string]: any };
export default class Draggable extends React.Component<PropsType, StateType> {
node: HTMLDivElement | null | undefined;
render() {
return (
<div
className={classNames(
{ draggable: !this.props?.deactivated, dragging_opacity: this.state?.hide_original },
this.props.className,
)}
ref={node => {
this.node = node;
if (this.props.refDraggable) {
this.props.refDraggable(node);
}
}}
onClick={this.props.onClick}
onMouseDown={evt => {
!this.props?.deactivated && DraggableManager.start(evt, this.node, this);
}}
onMouseUp={evt => {
!this.props?.deactivated && DraggableManager.end(evt, this.node, this);
}}
onMouseOver={this.props.onMouseOver}
onMouseOut={this.props.onMouseOut}
onDoubleClick={this.props.onDoubleClick}
>
{this.props.children}
</div>
);
}
}
@@ -1,74 +0,0 @@
/* eslint-disable react/prop-types */
import React, { Component } from 'react';
import DraggableManager from './draggable-manager.js';
import Number from 'app/features/global/utils/Numbers';
import './draggable.scss';
export default class DroppableZone extends Component {
constructor() {
super();
this.unique_drop_key = Number.unid();
this.longOverTimeout = '';
}
onDrop(data) {
if (!data) {
return;
}
if (this.props.types && data.type) {
if (this.props.types.indexOf(data.type) < 0) {
return;
}
}
if (this.props.onDrop) {
this.props.onDrop(data);
}
}
componentWillUnmount() {
DraggableManager.removeDropCallback(this.unique_drop_key);
clearTimeout(this.longOverTimeout);
}
onEnter() {
if (DraggableManager.drag) {
this.longOverTimeout = setTimeout(() => {
if (DraggableManager.drag) {
if (this.props.onClick && !this.props.disableLongOver) {
this.props.onClick();
}
if (this.props.onLongOver) {
this.props.onLongOver();
}
}
}, 1500);
DraggableManager.setDropCallback(this.unique_drop_key, d => this.onDrop(d));
}
}
onLeave() {
DraggableManager.removeDropCallback(this.unique_drop_key);
clearTimeout(this.longOverTimeout);
}
render() {
return (
<div
className={this.props.className}
style={this.props.style}
onClick={this.props.onClick}
onMouseEnter={() => {
if (!this.props.deactivated) {
this.onEnter();
}
}}
onMouseLeave={() => {
if (!this.props.deactivated) {
this.onLeave();
}
}}
>
{this.props.children}
{!this.props.children && !this.props.deactivated && (
<div className="empty">{this.props.empty || ''}</div>
)}
</div>
);
}
}
@@ -1,87 +0,0 @@
import React from 'react';
import DriveElement from './drive-element.jsx';
import UIDirectory from './ui/directory.jsx';
import Loader from 'components/loader/loader.jsx';
import WorkspaceUserRights from 'app/features/workspaces/services/workspace-user-rights-service';
import Draggable from 'components/draggable/draggable';
import DroppableZone from 'components/draggable/droppable-zone.jsx';
import Languages from 'app/features/global/services/languages-service';
import './drive.scss';
export default class Directory extends DriveElement {
constructor(props) {
super();
this.props = props;
this.state = {
loading: true,
};
}
componentDidMount() {
super.componentDidMount();
if (this.state.loading) {
setTimeout(() => {
this.setState({ loading: false });
}, 4000);
}
}
render() {
if (!this.state.element || !this.state.element.front_id) {
return (
<div className="directory mini" style={{ textAlign: 'center' }}>
{this.state.loading && <Loader color="#CCC" className="file_loader" />}
{!this.state.loading && (
<span className="text" style={{ opacity: 0.5 }}>
{Languages.t(
'components.drive.navigators.directory_not_found',
[],
'Directory not found.',
)}
</span>
)}
</div>
);
} else {
this.state.loading = false;
}
if (this.props.hide) {
return '';
}
return (
<DroppableZone
types={['file']}
onDrop={data => this.dropFile(data)}
onLongOver={this.props.onClick}
className="directory_drop_zone"
>
<Draggable
style={this.props.style}
className={
'js-drive-multi-selector-selectable fade_in directory ' +
(this.state.selected ? 'is_selected ' : '') +
this.props.className
}
deactivated={
(this.state.element.is_directory && this.state.element.application_id) ||
WorkspaceUserRights.isNotConnected()
}
refDraggable={node => (this.node = node)}
onClick={evt => {
this.clickElement(evt);
}}
onDoubleClick={this.props.onDoubleClick}
parentClassOnDrag="grid"
onDragStart={evt => {
this.dragElement(evt);
}}
minMove={10}
data={{ type: 'file', selection_type: this.props.selectionType, data: this.props.data }}
>
<UIDirectory data={this.state.element} menu={this.common_menu} details={true} />
</Draggable>
</DroppableZone>
);
}
}
@@ -1,645 +0,0 @@
/* eslint-disable react/prop-types */
/* eslint-disable react/no-direct-mutation-state */
import React, { useState } from 'react';
import SelectionsManager from 'app/deprecated/SelectionsManager/SelectionsManager.js';
import DriveService from 'app/deprecated/Apps/Drive/Drive.js';
import AlertManager from 'app/features/global/services/alert-manager-service';
import MenuManager from 'app/components/menus/menus-manager.jsx';
import Workspaces from 'app/deprecated/workspaces/workspaces.jsx';
import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js';
import FilePicker from 'components/drive/file-picker/file-picker.jsx';
import Button from 'components/buttons/button.jsx';
import Input from 'components/inputs/input.jsx';
import VersionDetails from './version-details.jsx';
import WorkspacesApps from 'app/deprecated/workspaces/workspaces_apps.jsx';
import InputWithClipBoard from 'components/input-with-clip-board/input-with-clip-board.jsx';
import WorkspaceUserRights from 'app/features/workspaces/services/workspace-user-rights-service';
import MediumPopupManager from 'app/components/modal/modal-manager';
import Languages from 'app/features/global/services/languages-service';
import TagPicker from 'components/tag-picker/tag-picker.jsx';
import RouterServices from 'app/features/router/services/router-service';
import { getAsFrontUrl } from 'app/features/global/utils/URLUtils';
import FeatureTogglesService, {
FeatureNames,
} from 'app/features/global/services/feature-toggles-service';
import ModalManager from 'app/components/modal/modal-manager';
import LockedOnlyOfficePopup from 'app/components/locked-features-components/locked-only-office-popup/locked-only-office-popup';
const RenameInput = props => {
const [value, setValue] = useState(props.value);
return (
<div>
<div className="menu-buttons">
<Input
onEnter={() => {
if ((value || '').trim().length > 0) {
props.rename(value);
}
}}
className="full_width bottom-margin"
onEchap={() => {
MenuManager.closeMenu();
}}
autoFocus
value={value}
onChange={evt => setValue(evt.target.value)}
/>
</div>
<div className="menu-buttons">
<Button
disabled={(value || '').trim().length <= 0}
type="button"
value={Languages.t('general.save', [], 'Enregistrer')}
onClick={() => {
props.rename(value);
}}
/>
</div>
</div>
);
};
const PublicSharing = props => {
Collections.get('drive').useListener(useState);
const element = Collections.get('drive').find(props?.id);
if (!element) {
return <div />;
}
const sharedUrl = RouterServices.generateRouteFromState({
workspaceId: element.workspace_id,
documentId: element?.id,
appName: 'drive',
shared: true,
token: (element.acces_info || {}).token,
});
return (
<div>
{(element.acces_info || {}).token && (
<div>
<InputWithClipBoard
className={'bottom-margin full_width'}
value={getAsFrontUrl(sharedUrl)}
disabled={false}
/>
<br />
<br />
<Button
className="danger"
onClick={() => {
AlertManager.confirm(() => {
DriveService.updateAccess(
element,
{ public_access: false },
props.driveCollectionKey,
);
});
}}
value={Languages.t(
'components.drive.right_preview.suppress_link',
[],
'Supprimer le lien',
)}
/>
</div>
)}
{!(element.acces_info || {}).token && (
<div>
<Button
onClick={() => {
DriveService.updateAccess(element, { public_access: true }, props.driveCollectionKey);
}}
value={Languages.t(
'components.drive.right_preview.create_link',
[],
"Créer un lien d'accès",
)}
/>
</div>
)}
</div>
);
};
export default class DriveElement extends React.Component {
constructor() {
super();
this.state = {
selections_manager: SelectionsManager,
};
SelectionsManager.addListener(this);
this.driveCollectionKey = '';
this.driveSelectorOver = this.driveSelectorOver.bind(this);
this.driveSelectorOut = this.driveSelectorOut.bind(this);
Collections.get('drive').addListener(this);
}
componentWillUnmount() {
SelectionsManager.removeListener(this);
Collections.get('drive').removeListener(this);
if (this.node) {
this.node.removeEventListener('drive_selector_over', this.driveSelectorOver);
this.node.removeEventListener('drive_selector_out', this.driveSelectorOut);
}
if (this.driveCollectionKey && this.driveCollectionKey != this.props.driveCollectionKey) {
Collections.get('drive').removeSource(this.driveCollectionKey);
}
}
UNSAFE_componentWillMount() {
if (this.props && this.props.data && this.props.data.front_id) {
Collections.get('drive').listenOnly(this, [this.props.data.front_id]);
}
this.state.element = DriveService.find(
Workspaces.currentWorkspaceId,
this.props.data?.id,
el => {
this.setState({ element: el });
},
);
this.updateMenu();
}
componentDidMount() {
if (this.node) {
this.node.addEventListener('drive_selector_over', this.driveSelectorOver);
this.node.addEventListener('drive_selector_out', this.driveSelectorOut);
this.node.setAttribute('drive_selector_unid', this.state.element?.id);
}
if (this.props && this.props.data && this.props.data?.id) {
SelectionsManager.listenOnly(this, [this.props.data?.id]);
}
}
driveSelectorOut() {
if (this.state.selected) this.setState({ selected: false });
}
driveSelectorOver() {
if (!this.state.selected) this.setState({ selected: true });
}
dropFile(data, directory) {
var destination = directory || this.props.data;
var objects = data.data?.id;
if (data.selection_type) {
var selected = Object.keys(SelectionsManager.selected_per_type[data.selection_type] || {});
if (selected && selected.length > 1) {
objects = selected;
}
}
DriveService.moveFile(
objects,
destination,
this.props.driveCollectionKey || this.driveCollectionKey,
);
}
dragElement(evt) {
SelectionsManager.setType(this.props.selectionType);
if (
!evt.shiftKey &&
!SelectionsManager.selected_per_type[this.props.selectionType][this.state.element?.id]
) {
SelectionsManager.unselectAll();
}
SelectionsManager.select(this.state.element?.id);
}
clickElement(evt, previewonly = false) {
evt.stopPropagation();
evt.preventDefault();
if (this.props.notInDrive) {
if (this.props.onClick) {
this.props.onClick();
} else {
DriveService.viewDocument(this.state.element, previewonly);
}
} else {
SelectionsManager.setType(this.props.selectionType);
if (!evt.shiftKey) {
if (this.props.onClick) {
this.props.onClick();
} else {
DriveService.viewDocument(this.state.element, previewonly);
}
var oldStatus =
SelectionsManager.selected_per_type[this.props.selectionType][this.state.element?.id];
SelectionsManager.unselectAll();
if (oldStatus) {
SelectionsManager.unselect(this.state.element?.id);
} else {
SelectionsManager.select(this.state.element?.id);
}
} else {
SelectionsManager.toggle(this.state.element?.id);
}
}
}
UNSAFE_componentWillUpdate(nextProps, nextState) {
nextState.element =
Collections.get('drive').find(this.props.data?.id) ||
DriveService.find(Workspaces.currentWorkspaceId, this.props.data?.id, el => {
this.setState({ element: el });
});
this.state.element = nextState.element;
if (
this.state.element &&
SelectionsManager.selected_per_type[nextProps.selectionType] &&
SelectionsManager.selected_per_type[nextProps.selectionType][this.state.element?.id] !=
this.old_selection_state
) {
nextState.selected =
SelectionsManager.selected_per_type[nextProps.selectionType][nextProps.data?.id];
this.old_selection_state = nextState.selected;
}
if (this.state.element && !this.props.driveCollectionKey) {
this.channel = 'standalone_drive_collection_' + this.state.element.parent_id;
var parent_id = this.state.element.parent_id;
if (this.state.element.parent_id == 'detached') {
parent_id += '_' + this.state.element.workspace_id;
}
var old_collection_key = this.driveCollectionKey;
this.driveCollectionKey = DriveService.addSourceIfNotExist(
Workspaces.currentWorkspaceId,
this.channel,
parent_id,
'standalone',
);
if (old_collection_key && old_collection_key != this.driveCollectionKey) {
Collections.get('drive').removeSource(old_collection_key);
}
}
this.updateMenu();
return true;
}
moveTo(new_parent) {
DriveService.moveFile(
[this.state.element?.id],
new_parent,
this.props.driveCollectionKey || this.driveCollectionKey,
);
}
rename(new_name) {
if (!this.state.element.is_directory) {
new_name = new_name + '.' + this.state.element.extension;
}
this.state.element.name = new_name || this.state.element.name;
new_name = undefined;
MenuManager.closeMenu();
DriveService.save(this.state.element, this.props.driveCollectionKey || this.driveCollectionKey);
}
updateMenu() {
if (!this.state.element) {
this.common_menu = [];
return;
}
this.common_menu = [];
//-- All files
if (!this.state.element.is_directory) {
this.common_menu = [
{
type: 'menu',
text: Languages.t('components.drive.elements.see', [], 'Voir'),
onClick: () => {
DriveService.viewDocument(this.state.element);
},
},
];
var candidates = DriveService.getEditorsCandidates(this.state.element);
var editor_candidate = candidates.preview_candidate || [];
if (editor_candidate.length > 0 && editor_candidate[0].app) {
this.common_menu.push({
type: 'menu',
text: Languages.t(
'scenes.apps.drive.viewer.edit_with_button',
[editor_candidate[0].app?.identity?.name],
'Editer avec $1',
),
onClick: () => {
if (FeatureTogglesService.isActiveFeatureName(FeatureNames.EDIT_FILES)) {
var app = editor_candidate[0];
if (app.url && app.is_url_file) {
window.open(app.url);
}
app = app.app;
DriveService.getFileUrlForEdition(
app.display?.tdrive?.files?.editor?.edition_url,
app,
this.state.element?.id,
url => {
window.open(url);
},
);
} else {
ModalManager.open(
<LockedOnlyOfficePopup />,
{
position: 'center',
size: { width: '600px' },
},
false,
);
}
},
});
}
}
this.common_menu.push({
type: 'menu',
text:
this.state.element.url && this.state.element.extension == 'url'
? Languages.t('scenes.apps.drive.open_link', [], 'Open link')
: Languages.t('scenes.apps.drive.download_button', [], 'Télécharger'),
onClick: () => {
var link = DriveService.getLink(this.state.element, undefined, 1);
if (this.state.element.url) {
window.open(link, '_blank');
} else {
window.open(link);
}
},
});
if (!WorkspaceUserRights.isNotConnected()) {
//-- All files
if (this.state.element.detached) {
this.common_menu.push({
type: 'menu',
text: Languages.t('scenes.apps.drive.move_text2', [], 'Déplacer'),
submenu_replace: true,
submenu: [
{
type: 'react-element',
reactElement: () => (
<FilePicker
mode={'select_location'}
onChoose={res => this.moveTo(res)}
initialDirectory={{ id: '' }}
/>
),
},
],
});
}
if (!this.props.notInDrive && !this.state.element.detached) {
if (this.common_menu.length > 0) {
this.common_menu.push({ type: 'separator' });
}
if (!this.state.element.detached && this.state.element.parent_id) {
this.common_menu = this.common_menu.concat([
{
type: 'menu',
text: Languages.t('scenes.app.mainview.tabs.rename', [], 'Renommer'),
submenu_replace: true,
submenu: [
{
type: 'title',
text: Languages.t('scenes.app.mainview.tabs.rename', [], 'Renommer'),
},
{
type: 'text',
text:
Languages.t('components.drive.elements.current_name', [], 'Nom actuel : ') +
this.state.element.name,
},
{
type: 'react-element',
reactElement: () => (
<RenameInput
rename={name => {
this.rename(name);
}}
value={
this.state.element.is_directory
? this.state.element.name
: this.state.element.name.replace(/\.[^.]*$/, '')
}
/>
),
},
],
},
]);
}
if (!this.state.element.trash) {
this.common_menu = this.common_menu.concat([
{
type: 'menu',
text: Languages.t('scenes.apps.drive.right_preview.public', [], 'Accès public...'),
submenu_replace: true,
submenu: [
{
type: 'title',
text: Languages.t(
'scenes.apps.drive.right_preview.public_link',
[],
"Lien d'accès public",
),
},
{
//TODO menu react-element to refactor
type: 'react-element',
reactElement: () => (
<PublicSharing
id={this.state.element?.id}
element={this.state.element}
driveCollectionKey={this.props.driveCollectionKey || this.driveCollectionKey}
/>
),
},
],
},
]);
if (!(this.state.element.application_id && this.state.element.is_directory)) {
this.common_menu.push({
type: 'menu',
text: Languages.t('scenes.apps.drive.move_text', [], 'Déplacer'),
submenu_replace: true,
submenu: [
{
type: 'react-element',
reactElement: () => (
<FilePicker
mode={'select_location'}
onChoose={res => this.moveTo(res)}
initialDirectory={{ id: this.state.element.parent_id }}
/>
),
},
],
});
}
if (WorkspaceUserRights.hasWorkspacePrivilege()) {
if (this.state.element.application_id && this.state.element.is_directory) {
this.common_menu.push({
type: 'menu',
text: Languages.t(
'components.drive.elements.configurate_mod',
[],
'Configurer le module...',
),
onClick: () => {
var data = {
drive_element: this.state.element,
};
WorkspacesApps.notifyApp(
this.state.element.application_id,
'configuration',
'drive',
data,
);
},
});
}
}
if (!this.state.element.is_directory && !this.state.element.url) {
var versions_indicator = '';
if ((this.state.element.versions || {}).length > 1) {
versions_indicator = ' (' + (this.state.element.versions || {}).length + ')';
}
this.common_menu.push(
{
type: 'menu',
text:
Languages.t(
'components.drive.elements.manage_version',
[],
'Gérer les versions',
) +
versions_indicator +
'...',
onClick: () => {
MediumPopupManager.open(<VersionDetails file={this.state.element} />, {
size: { width: 600 },
});
},
},
{
type: 'menu',
text: Languages.t(
'scenes.apps.drive.navigators.navigator_labels.title',
[],
'Labels',
),
submenu_replace: true,
submenu: [
{
type: 'title',
text: Languages.t(
'scenes.apps.drive.navigators.navigator_labels.title',
[],
'Labels',
),
},
{
type: 'react-element',
reactElement: level => (
<div>
<TagPicker
menu_level={level}
saveButton
canCreate={true}
value={this.state.element.tags || []}
onChange={values => {
this.state.element.tags = values;
MenuManager.closeMenu();
DriveService.save(
this.state.element,
this.props.driveCollectionKey || this.driveCollectionKey,
);
}}
/>
</div>
),
},
],
},
);
}
if (this.props.attachmentMenu) {
this.common_menu.push(this.props.attachment_menu);
}
this.common_menu.push({ type: 'separator' });
this.common_menu.push({
type: 'menu',
text: Languages.t(
'scenes.apps.drive.right_preview.operations_delete',
[],
'Mettre à la corbeille',
),
className: 'error',
onClick: () => {
DriveService.remove(
[this.props.data],
this.props.driveCollectionKey || this.driveCollectionKey,
);
},
});
} else {
this.common_menu.push({
type: 'menu',
text: Languages.t(
'scenes.apps.drive.right_preview.operations_restore',
[],
'Restaurer',
),
onClick: () => {
DriveService.restore(
[this.props.data],
this.props.driveCollectionKey || this.driveCollectionKey,
);
},
});
this.common_menu.push({
type: 'menu',
text: Languages.t(
'scenes.apps.drive.remove_definitely_menu',
[],
'Supprimer définitivement',
),
className: 'error',
onClick: () => {
AlertManager.confirm(() => {
DriveService.removeDefinitively(
[this.props.data],
this.props.driveCollectionKey || this.driveCollectionKey,
);
});
},
});
}
}
if (this.props.additionalMenu && this.props.additionalMenu.length > 0) {
this.common_menu.push({ type: 'separator' });
this.common_menu = this.common_menu.concat(this.props.additionalMenu);
}
}
}
}
@@ -1,278 +0,0 @@
/* eslint-disable react/prop-types */
/* eslint-disable react/no-direct-mutation-state */
import React, { Component } from 'react';
import './drive-multi-selector.scss';
import SelectionsManager from 'app/deprecated/SelectionsManager/SelectionsManager.js';
import './drive.scss';
export default class DriveMultiSelector extends Component {
constructor() {
super();
this.state = {
start_drag: false,
dragging: true,
};
this.move = this.move.bind(this);
this.end = this.end.bind(this);
this.start = this.start.bind(this);
this.pause = this.pause.bind(this);
this.unpause = this.unpause.bind(this);
this.currentSelection = {};
this.currentSelectionCount = 0;
this.noMovementRefreshAnyway = setTimeout('');
}
componentDidMount() {
this.scroller_container = this.props.scroller;
}
componentWillUnmount() {
clearInterval(this.noMovementRefreshAnyway);
this.container.removeEventListener('mouseleave', this.pause);
this.container.removeEventListener('mouseenter', this.unpause);
document.removeEventListener('mousemove', this.move);
document.removeEventListener('mouseup', this.end);
document.body.classList.remove('no_select');
}
pause() {
clearInterval(this.noMovementRefreshAnyway);
this.setState({ dragging: false });
}
unpause() {
this.setState({ dragging: true });
}
end(evt) {
clearInterval(this.noMovementRefreshAnyway);
this.setState({ start_drag: false });
document.removeEventListener('mousemove', this.move);
document.removeEventListener('mouseup', this.end);
this.container.removeEventListener('mouseleave', this.pause);
this.container.removeEventListener('mouseenter', this.unpause);
document.body.classList.remove('no_select');
if (!this.did_drag) {
if (!evt.shiftKey && !this.did_start_drag == 0) {
SelectionsManager.unselectAll();
}
this.did_start_drag = false;
return;
}
this.did_drag = false;
Object.keys(this.currentSelection).forEach(id => {
if (this.currentSelection[id]) {
SelectionsManager.select(id);
} else {
SelectionsManager.unselect(id);
}
});
this.did_start_drag = false;
this.noMovementRefreshAnyway = setTimeout(() => {
this.state.left = 0;
this.state.right = 0;
this.state.width = 0;
this.state.height = 0;
this.setState({});
}, 500);
}
start(evt) {
this.did_drag = false;
this.did_start_drag = true;
this.element_position_cache = {};
SelectionsManager.setType(this.props.selectionType);
clearInterval(this.noMovementRefreshAnyway);
this.currentSelection = {};
this.state.start_drag = true;
this.state.dragging = true;
var rect = window.getBoundingClientRect(this.container);
rect.x = rect.x || rect.left;
rect.y = rect.y || rect.top;
this.drag_add = evt.nativeEvent.shiftKey;
this.drag_start = [
evt.clientX - rect.left,
evt.clientY - rect.top,
this.container.scrollLeft,
this.container.scrollTop,
];
document.addEventListener('mousemove', this.move);
document.addEventListener('mouseup', this.end);
this.container.addEventListener('mouseleave', this.pause);
this.container.addEventListener('mouseenter', this.unpause);
document.body.classList.add('no_select');
rect = {};
rect.width = 0;
rect.height = 0;
rect.left = evt.clientX;
rect.top = evt.clientY;
this.autoSelect(rect);
}
move(evt) {
clearInterval(this.noMovementRefreshAnyway);
var rect = window.getBoundingClientRect(this.container);
rect.x = rect.x || rect.left;
rect.y = rect.y || rect.top;
var pos = [];
if (this.state.start_drag && this.state.dragging) {
this.did_drag = true;
pos = [evt.clientX - rect.left, evt.clientY - rect.top];
pos[0] = Math.min(pos[0], rect.width - 5);
pos[0] = Math.max(pos[0], 5);
pos[1] = Math.min(pos[1], rect.height - 5);
pos[1] = Math.max(pos[1], 5);
var w = this.drag_start[0] + this.drag_start[2] - (pos[0] + this.container.scrollLeft);
var h = this.drag_start[1] + this.drag_start[3] - (pos[1] + this.container.scrollTop);
var winv = w > 0;
var hinv = h > 0;
var selectorRect = {
width: Math.abs(w),
height: Math.abs(h),
left: this.drag_start[0] + this.drag_start[2] - Math.abs(winv ? w : 0),
top: this.drag_start[1] + this.drag_start[3] - Math.abs(hinv ? h : 0),
};
this.setState(selectorRect);
rect = window.getBoundingClientRect(this.selector);
rect.x = rect.x || rect.left;
rect.y = rect.y || rect.top;
this.autoSelect(rect);
}
pos = [evt.clientX, evt.clientY];
if (
rect.height + rect.top < pos[1] ||
rect.top > pos[1] ||
rect.width + rect.left < pos[0] ||
rect.left > pos[0]
) {
var newScrollTop =
this.container.scrollTop +
(pos[1] - ((pos[1] > rect.height + rect.top ? rect.height : 0) + rect.top)) / 10;
var newScrollLeft =
this.container.scrollLeft +
(pos[0] - ((pos[0] > rect.width + rect.top ? rect.width : 0) + rect.left)) / 10;
if (this.container.scrollTop != newScrollTop || this.container.scrollLeft != newScrollLeft) {
this.container.scrollTop = newScrollTop;
this.container.scrollLeft = newScrollLeft;
this.state.dragging = true;
this.noMovementRefreshAnyway = setTimeout(() => {
this.move({
clientX: evt.clientX,
clientY: evt.clientY,
});
}, 50);
} else {
this.state.dragging = false;
}
}
}
autoSelect(rect) {
this.currentSelectionCount = 0;
var scroll_top = this.container.scrollTop;
if (this.old_scroll_top != scroll_top) {
this.element_position_cache = {};
}
this.old_scroll_top = scroll_top;
Array.from(this.container.getElementsByClassName('js-drive-multi-selector-selectable')).forEach(
element => {
var element_id = element.getAttribute('drive_selector_unid');
var selected = SelectionsManager.selected_per_type[this.props.selectionType][element_id];
var over = false;
var elementRect = {};
if (this.element_position_cache[element_id]) {
elementRect = JSON.parse(JSON.stringify(this.element_position_cache[element_id]));
} else {
var er = window.getBoundingClientRect(element);
er.x = er.x || er.left;
er.y = er.y || er.top;
elementRect = JSON.parse(JSON.stringify(er));
this.element_position_cache[element_id] = elementRect;
}
if (
!(
rect.left > elementRect.left + elementRect.width ||
elementRect.left > rect.left + rect.width ||
rect.top > elementRect.top + elementRect.height ||
elementRect.top > rect.top + rect.height
)
) {
over = true;
}
if (
(over && this.drag_add && selected) ||
(!over && this.drag_add && !selected) ||
(!over && !this.drag_add)
) {
element.dispatchEvent(new Event('drive_selector_out'));
this.currentSelection[element_id] = false;
}
if (
(over && this.drag_add && !selected) ||
(over && !this.drag_add) ||
(!over && this.drag_add && selected)
) {
element.dispatchEvent(new Event('drive_selector_over'));
this.currentSelection[element_id] = true;
this.currentSelectionCount++;
}
},
);
}
render() {
return (
<div
onMouseDown={evt => {
this.start(evt);
}}
onMouseUp={evt => {
this.end(evt);
}}
ref={node => (this.container = node)}
style={this.props.style || {}}
className={'drive_multiselector'}
>
<div
ref={node => (this.selector = node)}
className={
'selectionRect ' + (this.state.start_drag && this.state.dragging ? 'visible ' : '')
}
style={{
width: this.state.width,
height: this.state.height,
left: this.state.left,
top: this.state.top,
}}
/>
{this.props.children}
</div>
);
}
}
@@ -1,28 +0,0 @@
body.no_select {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.drive_multiselector {
position: relative;
overflow-x: visible;
overflow-y: scroll;
}
.selectionRect {
background: var(--primary);
z-index: 10;
border-radius: var(--border-radius-base);
position: absolute;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s;
&.visible {
opacity: 0.2;
}
}
@@ -1,522 +0,0 @@
/* Drive */
.directory,
.file {
.file_loader {
width: 32px;
height: 32px;
display: inline-block;
margin: auto;
}
.options {
padding-left: 5px;
display: none;
}
&:hover {
.options {
display: block;
}
}
}
.directory_drop_zone {
display: inline-block;
}
.directory {
background: #fff;
border: 1px solid var(--grey-background);
height: 50px;
box-sizing: border-box;
display: inline-flex;
flex-direction: row;
line-height: 32px;
margin-right: 16px;
margin-bottom: 16px;
border-radius: var(--border-radius-base);
font-size: 13px;
vertical-align: middle;
width: 200px;
padding: 8px;
font-weight: 500;
padding-right: 12px;
cursor: pointer;
.text {
font-size: 12px;
font-weight: bold;
}
&:hover:not(.notInDrive) {
box-shadow: 4px 4px 16px 0 #00000022;
border-color: #fff;
}
.options {
.m-icon-small {
font-size: 20px !important;
}
}
.app_icon {
width: 20px;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
height: 20px;
margin-top: 2px;
}
.icon {
font-size: 14px;
padding-right: 8px;
margin-left: 6px;
padding-top: 4px;
.m-icon-small {
color: var(--warning);
font-size: 20px !important;
}
i.icon-fontastic {
position: relative;
top: 8px;
}
.icon {
height: 30px;
background-size: 16px auto;
background-position: center left;
background-repeat: no-repeat;
}
}
}
.file {
overflow: hidden;
border: 1px solid var(--grey-background);
background: #fff;
height: 180px;
width: 216px;
box-sizing: border-box;
display: inline-flex;
flex-direction: column;
line-height: 30px;
margin-right: 16px;
margin-bottom: 16px;
border-radius: var(--border-radius-base);
font-size: 13px;
vertical-align: middle;
position: relative;
cursor: pointer;
&:hover:not(.notInDrive) {
box-shadow: 4px 4px 16px 0 #00000022;
border-color: #fff;
}
.options {
.m-icon-small {
font-size: 20px !important;
}
}
&.mini {
height: 32px;
.data {
border-top: none;
.no-grid {
display: none;
}
}
.preview {
height: 32px;
background: #fff !important;
&:after {
content: none;
}
}
}
.preview {
width: 216px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
background-color: #f5f5f7;
flex: 1;
.tags_list {
position: absolute;
top: 4px;
padding-left: 8px;
line-height: 30px;
}
&.no_preview {
background-color: #f5f5f7;
background-repeat: no-repeat;
background-size: 64px;
}
/*&:after {
content: "";
position: absolute;
width: 100%;
height: 100%;
background: linear-gradient(180deg, rgba(0,0,0,0) 40%, rgba(0,0,0,0.05) 70%, rgba(0,0,0,0.5) 100%);
}*/
}
.data {
display: flex;
flex-direction: row;
padding-left: 8px;
padding-right: 8px;
bottom: 0px;
color: var(--black);
height: 32px;
font-weight: 500;
width: 100%;
box-sizing: border-box;
border-top: 1px solid var(--grey-background);
.icon {
height: 20px;
width: 20px;
box-sizing: border-box;
border-radius: var(--border-radius-base);
margin-right: 6px;
margin-left: -3px;
margin-top: 5px;
color: #fff;
text-align: center;
background-size: 70%;
background-repeat: no-repeat;
background-position: center;
i.icon-fontastic {
position: relative;
top: 4px;
}
}
}
}
.draggable_clone {
.file,
.directory {
opacity: 1;
box-shadow: 4px 4px 16px 0 #00000022;
border-color: #fff;
}
}
.dragging.drag_file .drive_multiselector {
.file.is_selected,
.directory.is_selected {
opacity: 0;
transition: opacity 0.2s;
}
}
.file,
.directory {
transition: opacity 0s;
&.notInDrive {
margin-bottom: 0px;
}
&.dragging_opacity {
opacity: 0;
}
&.is_selected {
background: var(--primary);
color: #fff;
box-shadow: none;
.file_type_icon svg,
.file_type_icon svg * {
fill: #fff;
}
&:hover:not(.notInDrive),
.data {
background-color: var(--primary);
color: #fff;
}
}
.text {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.created-by,
.last-modified {
width: 200px;
}
.size {
text-align: right;
width: 70px;
}
.last-modified {
text-align: right;
width: 120px;
}
.detail_preview_parent {
width: 32px;
margin-left: 12px;
.detail_preview {
width: 32px;
height: 32px;
border-radius: var(--border-radius-base);
background-position: center;
background-repeat: no-repeat;
background-size: cover;
background-color: var(--primary-background);
&.no_preview {
background-size: 60%;
}
}
}
}
.drive_view.list {
.directory_drop_zone {
display: inherit;
}
}
.drive_view.list .directory,
.drive_view.list .file {
margin-right: 0px;
margin-bottom: 0px;
border: 0;
border-bottom: 1px solid var(--grey-background);
border-top: 1px solid var(--grey-background);
border-radius: 2px;
width: 100%;
height: 64px;
padding-top: 16px;
color: var(--black);
margin-top: -1px;
padding-right: 12px;
&.exit-active,
&.enter-active {
margin-top: -1px !important;
}
.data {
border-top: 0px;
bottom: -1px;
}
.text {
font-size: 14px;
}
.options {
display: block;
}
&:hover:not(.notInDrive):not(.is_selected) {
box-shadow: none;
background: var(--primary-background);
}
&.is_selected {
background: var(--primary);
color: #fff;
box-shadow: none;
}
}
.drive_view.list .file .preview {
display: none;
}
.drive_view.list {
.rounded-btn {
margin-top: 10px;
}
}
.drive_view.grid {
.is_selected.file {
background: var(--primary);
color: #fff;
box-shadow: none;
}
}
.drive_view.grid .no-grid {
display: none;
}
.drive_view.list .no-list {
display: none;
}
.file_type_icon {
line-height: 38px;
width: 24px;
svg,
svg * {
fill: var(--primary);
}
&.link {
background-image: url('./icons/link.svg');
}
&.code {
background-image: url('./icons/code.svg');
}
&.document {
background-image: url('./icons/document.svg');
}
&.image {
background-image: url('./icons/image.svg');
}
&.pdf {
background-image: url('./icons/pdf.svg');
}
&.slides {
background-image: url('./icons/slides.svg');
}
&.sound, &.audio {
background-image: url('./icons/sound.svg');
}
&.spreadsheet {
background-image: url('./icons/spreadsheet.svg');
}
&.svg {
background-image: url('./icons/svg.svg');
}
&.video {
background-image: url('./icons/video.svg');
}
&.archive {
background-image: url('./icons/archive.svg');
}
&.other {
background-image: url('./icons/file.svg');
}
}
.drive_view.list .file_type_icon {
line-height: 38px;
width: 34px;
text-align: center;
}
/* version details */
.versionDetails {
min-height: 250px;
.text.no-more {
text-align: center;
width: 100%;
opacity: 0.5;
margin-top: 32px;
margin-bottom: 32px;
}
.version {
display: flex;
padding: 24px 0px;
border-bottom: solid 1px var(--grey-background);
.titleVersionDetails {
font-size: 24px;
color: var(--black);
font-weight: bold;
margin-bottom: 24px;
}
.info {
flex: 0.5;
margin-bottom: 8px;
.versionTitle {
font-size: 14px;
font-weight: bold;
color: var(--black);
}
.versionDate {
font-size: 14px;
color: var(--black);
}
}
.name {
flex: 0.5;
font-size: 14px;
line-height: 24px;
color: var(--black);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.more {
}
}
}
.footerVersionDetails {
display: flex;
.addVersion {
flex: 1;
.addVersionButton {
width: 100%;
height: 24px;
line-height: 24px;
}
.addVersionButton:hover {
cursor: pointer;
opacity: 0.8;
}
.icon {
display: inline-block;
width: 24px;
height: 24px;
.iconWithBackground {
width: 24px;
height: 24px;
display: inline-block;
background-color: var(--grey-background);
border-radius: var(--border-radius-base);
}
}
.footerTitle {
margin-left: 8px;
display: inline-block;
font-size: 14px;
font-weight: bold;
color: var(--black);
.addVersionButton:hover {
cursor: pointer;
opacity: 0.8;
}
}
}
}
@@ -1,213 +0,0 @@
/* eslint-disable react/prop-types */
import React, { Component } from 'react';
import DriveService from 'app/deprecated/Apps/Drive/Drive.js';
import Workspaces from 'app/deprecated/workspaces/workspaces.jsx';
import Languages from 'app/features/global/services/languages-service';
import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js';
import UIFile from '../ui/file.jsx';
import UIDirectory from '../ui/directory.jsx';
import LeftIcon from '@material-ui/icons/KeyboardArrowLeftOutlined';
import NewFolderIcon from '@material-ui/icons/CreateNewFolderOutlined';
import Menu from 'components/menus/menu.jsx';
import Button from 'components/buttons/button.jsx';
import Input from 'components/inputs/input.jsx';
import './file-picker.scss';
export default class FilePicker extends Component {
constructor() {
super();
this.drive_channel = 'file_picker_' + Workspaces.currentWorkspaceId;
this.drive_collection_key = this.drive_channel;
this.state = {
i18n: Languages,
drive_repository: Collections.get('drive'),
app_drive_service: DriveService,
current_selection: {},
creating_folder: false,
};
Languages.addListener(this);
Collections.get('drive').addListener(this);
DriveService.addListener(this);
}
componentWillUnmount() {
Languages.removeListener(this);
Collections.get('drive').removeListener(this);
DriveService.removeListener(this);
if (this.drive_channel) {
Collections.get('drive').removeSource(
this.state.app_drive_service.current_collection_key_channels[this.drive_channel],
);
}
}
componentDidMount() {
this.changeCurrentDirectory({ id: (this.props.initialDirectory || {}).id || '' });
}
clickOnFile(file) {
if (this.props.mode == 'select_file') {
this.setState({ current_selection: file });
}
}
changeCurrentDirectory(directory) {
if (this.props.mode == 'select_file') {
this.setState({ current_selection: {} });
}
if (this.props.mode == 'select_location') {
this.setState({ current_selection: directory });
}
DriveService.changeCurrentDirectory(this.drive_channel, directory);
}
submit() {
var result = null;
if (this.props.mode == 'select_file') {
result = this.state.current_selection;
}
if (this.props.mode == 'select_location') {
result = this.state.app_drive_service.current_directory_channels[this.drive_channel] || {};
}
Menu.closeAll();
if (this.props.onChoose) this.props.onChoose(result);
}
render() {
var workspace_id = Workspaces.currentWorkspaceId;
var directory =
this.state.app_drive_service.current_directory_channels[this.drive_channel] || {};
var directory_id = directory.id;
var filter_dir = {
workspace_id: workspace_id,
parent_id: directory_id,
is_directory: true,
trash: false,
};
var directories = this.state.drive_repository
.findBy(filter_dir)
.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
var filter_files = {
workspace_id: workspace_id,
parent_id: directory_id,
is_directory: false,
trash: false,
};
var files = this.state.drive_repository
.findBy(filter_files)
.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
var allow_go_parent = true;
var drive_channel = null;
if (!drive_channel && (this.props.initialDirectory || {}).id == directory_id) {
allow_go_parent = false;
}
return (
<div className="filepicker">
<div className="title">
{directory.parent_id && allow_go_parent && (
<LeftIcon
className="m-icon-small getback"
onClick={() => {
this.changeCurrentDirectory({ id: directory.parent_id });
}}
/>
)}{' '}
{directory.parent_id
? directory.name
: Languages.t('app.identity?.name.tdrive_drive', [], 'Documents')}
</div>
<div className="drive_view list">
{directories.map((item, index) => (
<div
key={'file_picker_dirs_' + index}
className="directory"
onClick={() => DriveService.changeCurrentDirectory(this.drive_channel, item)}
>
<UIDirectory data={item} />
</div>
))}
{files.map((item, index) => (
<div
key={'file_picker_files_' + index}
className={
'file ' +
(this.props.mode == 'select_location' ? 'disabled ' : '') +
(this.state.current_selection.id == item.id ? 'is_selected ' : '')
}
onClick={() => this.clickOnFile(item)}
>
<UIFile data={item} />
</div>
))}
</div>
{this.state.creating_folder && (
<div className="menu-buttons" style={{ display: 'flex' }}>
<Button
className="button small secondary"
style={{ float: 'left' }}
onClick={() => this.setState({ creating_folder: false })}
>
<LeftIcon className="m-icon-small" />
</Button>
<Input
style={{ margin: 0, marginLeft: 5, flex: 1 }}
className="small"
refInput={node => (node ? node.focus() : '')}
type="text"
defaultValue={''}
placeholder={Languages.t(
'scenes.apps.drive.navigators.navigator_content.directory_name',
[],
'Nom du dossier',
)}
onKeyPress={e => {
if (e.key === 'Enter') {
DriveService.createDirectory(
directory.workspace_id,
e.target.value,
directory,
DriveService.current_collection_key_channels[this.drive_channel],
res => {
this.changeCurrentDirectory(res);
},
);
this.setState({ creating_folder: false });
}
}}
/>
</div>
)}
{!this.state.creating_folder && (
<div className="menu-buttons">
{!this.state.creating_folder && directory.workspace_id && (
<Button
className="button small secondary"
style={{ float: 'left' }}
onClick={() => this.setState({ creating_folder: true })}
>
<NewFolderIcon className="m-icon-small" />
</Button>
)}
{this.props.mode == 'select_location' && (
<Button
className="small"
value={Languages.t('components.drive.moove_here', [], 'Déplacer ici')}
onClick={() => this.submit()}
/>
)}
{this.props.mode == 'select_file' && this.state.current_selection.id && (
<Button
className="small"
value={Languages.t('scenes.app.taskpicker.select', [], 'Sélectionner')}
onClick={() => this.submit()}
/>
)}
</div>
)}
</div>
);
}
}
@@ -1,84 +0,0 @@
.filepicker {
margin: -5px 0;
.no-filepicker {
display: none;
}
.file_type_icon {
width: 20px;
margin-right: 4px;
}
.title {
background: #ffffff;
margin-bottom: 0px;
font-size: 16px;
color: #000;
line-height: 20px;
padding-bottom: 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.getback {
background: var(--grey-background);
border-radius: var(--border-radius-base);
width: 20px;
height: 20px;
vertical-align: top;
margin-right: 5px;
margin-left: 0px;
cursor: pointer;
&:hover {
background: #f0f0f0;
}
}
.menu-buttons {
text-align: right;
padding: 8px 0px;
}
.drive_view.list {
height: 150px;
overflow: auto;
width: 100%;
.file .icon {
width: 20px;
background-size: 60%;
}
.directory .icon {
width: 24px;
}
.directory .app_icon {
height: 16px;
}
.file,
.directory {
height: 40px;
padding-top: 4px;
.text {
line-height: 30px;
margin-left: 6px;
}
.icon {
padding-top: 3px;
padding-right: 0px;
padding-left: 5px;
}
&.disabled {
opacity: 0.5;
pointer-events: none;
}
}
}
}
@@ -1,91 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react';
import Draggable from 'components/draggable/draggable';
import DriveElement from './drive-element.jsx';
import './drive.scss';
import UIFile from './ui/file.jsx';
import Loader from 'components/loader/loader.jsx';
import WorkspaceUserRights from 'app/features/workspaces/services/workspace-user-rights-service';
import Languages from 'app/features/global/services/languages-service';
type StateType = any;
type NodeType = any;
export default class File extends DriveElement {
node: NodeType;
state: StateType;
constructor() {
super();
this.state = {
loading: true,
};
}
componentDidMount() {
super.componentDidMount();
if (this.state.loading) {
setTimeout(() => {
this.setState({ loading: false });
}, 4000);
}
}
render() {
if (!this.state.element || !this.state.element.front_id) {
return (
<div
className={'file mini ' + (this.props.notInDrive ? 'notInDrive ' : '')}
style={{ textAlign: 'center' }}
>
{this.state.loading && <Loader color="#CCC" className="file_loader" />}
{!this.state.loading && (
<span className="text" style={{ opacity: 0.5 }}>
{Languages.t('scenes.apps.drive.preview_bloc.error_file', [], 'File not found.')}
</span>
)}
</div>
);
} else {
this.state.loading = false;
}
if (this.props.hide) {
return '';
}
const mini = (!this.state.element.has_preview && this.props.notInDrive) || this.props.mini;
return (
<Draggable
style={this.props.style}
className={
'js-drive-multi-selector-selectable file ' +
(this.state.selected ? 'is_selected ' : '') +
(this.props.notInDrive ? 'notInDrive ' : '') +
(mini ? 'mini ' : '') +
this.props.className
}
refDraggable={(node: NodeType) => (this.node = node)}
onClick={(evt: any) => this.clickElement(evt, this.props.previewonly)}
onDoubleClick={this.props.onDoubleClick}
parentClassOnDrag="drive_view list"
onDragStart={(evt: any) => {
this.dragElement(evt);
}}
minMove={10}
data={{ type: 'file', selection_type: this.props.selectionType, data: this.props.data }}
deactivated={WorkspaceUserRights.isNotConnected() || this.props.notInDrive}
>
<UIFile
data={this.state.element}
menu={!this.props.removeIcon && this.common_menu}
details={true}
removeIcon={this.props.removeIcon}
removeOnClick={(e: any) => {
e.preventDefault();
e.stopPropagation();
this.props.removeOnClick();
}}
/>
</Draggable>
);
}
}
@@ -1,19 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
const SvgComponent = props => (
<svg
width={(props.scale || 1) * 0.75 * 32}
height={(props.scale || 1) * 0.75 * 32}
viewBox="0 0 1792 1792"
{...props}
>
<path
scale={((props.scale || 1) * 0.75) / 56}
fill={props.fill || '#FFF'}
d="M768 384V256H640v128h128zm128 128V384H768v128h128zM768 640V512H640v128h128zm128 128V640H768v128h128zm700-388c18.667 18.667 34.667 44 48 76s20 61.333 20 88v1152c0 26.667-9.333 49.333-28 68s-41.333 28-68 28H224c-26.667 0-49.333-9.333-68-28s-28-41.333-28-68V96c0-26.667 9.333-49.333 28-68s41.333-28 68-28h896c26.667 0 56 6.667 88 20s57.333 29.333 76 48l312 312zm-444-244v376h376c-6.667-19.333-14-33-22-41l-313-313c-8-8-21.667-15.333-41-22zm384 1528V640h-416c-26.667 0-49.333-9.333-68-28s-28-41.333-28-68V128H896v128H768V128H256v1536h1280zM909 943l107 349c5.333 18 8 35.333 8 52 0 55.333-24.167 101.167-72.5 137.5S842 1536 768 1536s-135.167-18.167-183.5-54.5S512 1399.333 512 1344c0-16.667 2.667-34 8-52 14-42 54-174 120-396V768h128v128h79c14.667 0 27.667 4.333 39 13s19 20 23 34zm-141 465c35.333 0 65.5-6.333 90.5-19s37.5-27.667 37.5-45-12.5-32.333-37.5-45-55.167-19-90.5-19-65.5 6.333-90.5 19-37.5 27.667-37.5 45 12.5 32.333 37.5 45 55.167 19 90.5 19z"
/>
</svg>
);
export default SvgComponent;
@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="1792px" height="1792px" viewBox="0 0 1792 1792" enable-background="new 0 0 1792 1792" xml:space="preserve">
<path fill="#FFFFFF" d="M768,384V256H640v128H768z M896,512V384H768v128H896z M768,640V512H640v128H768z M896,768V640H768v128H896z
M1596,380c18.667,18.667,34.667,44,48,76s20,61.333,20,88v1152c0,26.667-9.333,49.333-28,68s-41.333,28-68,28H224
c-26.667,0-49.333-9.333-68-28s-28-41.333-28-68V96c0-26.667,9.333-49.333,28-68s41.333-28,68-28h896c26.667,0,56,6.667,88,20
s57.333,29.333,76,48L1596,380z M1152,136v376h376c-6.667-19.333-14-33-22-41l-313-313C1185,150,1171.333,142.667,1152,136z
M1536,1664V640h-416c-26.667,0-49.333-9.333-68-28s-28-41.333-28-68V128H896v128H768V128H256v1536H1536z M909,943l107,349
c5.333,18,8,35.333,8,52c0,55.333-24.167,101.167-72.5,137.5S842,1536,768,1536s-135.167-18.167-183.5-54.5S512,1399.333,512,1344
c0-16.667,2.667-34,8-52c14-42,54-174,120-396V768h128v128h79c14.667,0,27.667,4.333,39,13S905,929,909,943z M768,1408
c35.333,0,65.5-6.333,90.5-19s37.5-27.667,37.5-45s-12.5-32.333-37.5-45s-55.167-19-90.5-19s-65.5,6.333-90.5,19
s-37.5,27.667-37.5,45s12.5,32.333,37.5,45S732.667,1408,768,1408z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

@@ -1,19 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
const SvgComponent = props => (
<svg
width={(props.scale || 1) * 0.75 * 32}
height={(props.scale || 1) * 0.75 * 32}
viewBox="0 0 32 32"
{...props}
>
<path
scale={(props.scale || 1) * 0.75}
fill={props.fill || '#FFF'}
d="M10.786 22.646l-.737.738a.462.462 0 0 1-.68 0L2.494 16.51a.461.461 0 0 1 0-.678l6.875-6.876a.472.472 0 0 1 .341-.147c.128 0 .24.05.339.147l.737.738a.472.472 0 0 1 .147.341c0 .127-.05.239-.147.339l-5.798 5.798 5.798 5.798c.097.098.147.212.147.339s-.05.24-.147.337zm8.718-15.741l-5.502 19.048a.472.472 0 0 1-.229.287.415.415 0 0 1-.346.039l-.915-.254a.463.463 0 0 1-.288-.227.437.437 0 0 1-.037-.36l5.503-19.05a.495.495 0 0 1 .227-.288.417.417 0 0 1 .348-.036l.917.25c.128.04.223.115.288.228a.447.447 0 0 1 .034.363zM29.2 16.51l-6.879 6.875c-.097.102-.212.149-.339.149s-.241-.048-.338-.149l-.738-.738a.47.47 0 0 1-.146-.339.47.47 0 0 1 .146-.339l5.798-5.798-5.798-5.798a.474.474 0 0 1-.146-.339c0-.129.049-.242.146-.341l.738-.738a.464.464 0 0 1 .677 0l6.879 6.876a.465.465 0 0 1 0 .679z"
/>
</svg>
);
export default SvgComponent;
@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
<path fill="#FFFFFF" d="M10.786,22.646l-0.737,0.738c-0.1,0.102-0.211,0.149-0.339,0.149c-0.129,0-0.241-0.048-0.341-0.149
L2.494,16.51c-0.099-0.097-0.148-0.211-0.148-0.339c0-0.127,0.049-0.242,0.148-0.339l6.875-6.876c0.1-0.098,0.212-0.147,0.341-0.147
c0.128,0,0.24,0.05,0.339,0.147l0.737,0.738c0.097,0.099,0.147,0.211,0.147,0.341c0,0.127-0.05,0.239-0.147,0.339l-5.798,5.798
l5.798,5.798c0.097,0.098,0.147,0.212,0.147,0.339S10.883,22.549,10.786,22.646z M19.504,6.905l-5.502,19.048
c-0.039,0.127-0.115,0.225-0.229,0.287c-0.112,0.066-0.228,0.075-0.346,0.039l-0.915-0.254c-0.128-0.037-0.224-0.112-0.288-0.227
c-0.064-0.115-0.077-0.233-0.037-0.36l5.503-19.05c0.04-0.127,0.117-0.224,0.227-0.288c0.115-0.063,0.232-0.076,0.348-0.036
l0.917,0.25c0.128,0.04,0.223,0.115,0.288,0.228C19.531,6.657,19.546,6.778,19.504,6.905z M29.2,16.51l-6.879,6.875
c-0.097,0.102-0.212,0.149-0.339,0.149s-0.241-0.048-0.338-0.149l-0.738-0.738c-0.097-0.098-0.146-0.212-0.146-0.339
s0.049-0.241,0.146-0.339l5.798-5.798l-5.798-5.798c-0.097-0.1-0.146-0.212-0.146-0.339c0-0.129,0.049-0.242,0.146-0.341
l0.738-0.738c0.097-0.098,0.211-0.147,0.338-0.147s0.242,0.05,0.339,0.147l6.879,6.876c0.098,0.097,0.146,0.212,0.146,0.339
C29.346,16.298,29.298,16.413,29.2,16.51z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

@@ -1,20 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
const SvgComponent = props => (
<svg
width={(props.scale || 1) * 0.75 * 32}
height={(props.scale || 1) * 0.75 * 32}
viewBox="0 0 32 32"
{...props}
>
<g fill={props.fill || '#FFF'}>
<path
scale={(props.scale || 1) * 0.75}
d="M29.159 6.189H2.839a1.34 1.34 0 0 1 0-2.679h26.32a1.34 1.34 0 1 1 0 2.679zM29.159 13.592H2.839a1.34 1.34 0 0 1 0-2.68h26.32a1.34 1.34 0 0 1 0 2.68zM29.159 20.995H2.839a1.34 1.34 0 0 1 0-2.678h26.32a1.34 1.34 0 1 1 0 2.678zM18.685 28.403H2.839c-.739 0-1.339-.598-1.339-1.338s.601-1.341 1.339-1.341h15.845c.74 0 1.34.601 1.34 1.341s-.599 1.338-1.339 1.338z"
/>
</g>
</svg>
);
export default SvgComponent;
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
<g>
<path fill="#FFFFFF" d="M29.159,6.189H2.839C2.101,6.189,1.5,5.589,1.5,4.849C1.5,4.11,2.101,3.51,2.839,3.51h26.32
c0.74,0,1.341,0.6,1.341,1.339C30.5,5.589,29.899,6.189,29.159,6.189z"/>
<path fill="#FFFFFF" d="M29.159,13.592H2.839c-0.739,0-1.339-0.6-1.339-1.339c0-0.74,0.601-1.341,1.339-1.341h26.32
c0.74,0,1.341,0.601,1.341,1.341C30.5,12.992,29.899,13.592,29.159,13.592z"/>
<path fill="#FFFFFF" d="M29.159,20.995H2.839c-0.739,0-1.339-0.601-1.339-1.34s0.601-1.338,1.339-1.338h26.32
c0.74,0,1.341,0.599,1.341,1.338S29.899,20.995,29.159,20.995z"/>
<path fill="#FFFFFF" d="M18.685,28.403H2.839c-0.739,0-1.339-0.598-1.339-1.338s0.601-1.341,1.339-1.341h15.845
c0.74,0,1.34,0.601,1.34,1.341S19.425,28.403,18.685,28.403z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,19 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
const SvgComponent = props => (
<svg
width={(props.scale || 1) * 0.75 * 32}
height={(props.scale || 1) * 0.75 * 32}
viewBox="0 0 100 100"
{...props}
>
<path
scale={(props.scale || 1) * 0.75}
fill={props.fill || '#FFF'}
d="M60 0H20C13.373 0 8 5.373 8 12v72c0 6.627 5.373 12 12 12h55.875C82.502 96 88 90.627 88 84V28L60 0zm0 11.178L76.709 28H60V11.178zM75.875 88H20c-2.206 0-4-1.794-4-4V12c0-2.206 1.794-4 4-4h32v28h28v48c0 2.168-1.889 4-4.125 4z"
/>
</svg>
);
export default SvgComponent;
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="file" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="96px" height="96px" viewBox="0 0 96 96" enable-background="new 0 0 96 96" xml:space="preserve">
<path fill="#FFFFFF" d="M60,0H20C13.373,0,8,5.373,8,12v72c0,6.627,5.373,12,12,12h55.875C82.502,96,88,90.627,88,84V28
C84,24,66,6,60,0z M60,11.178L76.709,28H60V11.178z M75.875,88H20c-2.206,0-4-1.794-4-4V12c0-2.206,1.794-4,4-4h32v20v8h8h20v48
C80,86.168,78.111,88,75.875,88z"/>
</svg>

Before

Width:  |  Height:  |  Size: 704 B

@@ -1,24 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
const SvgComponent = props => (
<svg
width={(props.scale || 1) * 32 * 0.75}
height={(props.scale || 1) * 32 * 0.75}
viewBox="0 0 32 32"
{...props}
>
<g fill={props.fill || '#FFF'}>
<path
scale={(props.scale || 1) * 0.75}
d="M23.251 14.637a3.106 3.106 0 1 0-.002-6.212 3.106 3.106 0 0 0 .002 6.212z"
/>
<path
scale={(props.scale || 1) * 0.75}
d="M28.688 4.282H3.312c-1.01 0-1.812.822-1.812 1.831v21.194c0 1.008.802 1.831 1.812 1.831h25.376a1.82 1.82 0 0 0 1.812-1.831V6.113a1.819 1.819 0 0 0-1.812-1.831zm-6.673 12.796a1.116 1.116 0 0 0-.829-.402c-.328 0-.562.156-.829.37l-1.211 1.023c-.252.182-.452.303-.744.303-.278 0-.529-.102-.712-.263a8.251 8.251 0 0 1-.279-.268l-3.483-3.768a1.422 1.422 0 0 0-1.079-.485c-.434 0-.835.213-1.089.505l-8.187 9.88V7.11c.065-.441.409-.758.847-.758h23.154c.448 0 .81.329.837.776l.018 16.856-6.414-6.906z"
/>
</g>
</svg>
);
export default SvgComponent;
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
<g>
<path fill="#FFFFFF" d="M23.251,14.637c1.712,0,3.105-1.389,3.105-3.105c0-1.716-1.394-3.109-3.105-3.109
c-1.716,0-3.107,1.393-3.107,3.109C20.144,13.248,21.535,14.637,23.251,14.637z"/>
<path fill="#FFFFFF" d="M28.688,4.282H3.312C2.302,4.282,1.5,5.104,1.5,6.113v21.194c0,1.008,0.802,1.831,1.812,1.831h25.376
c1.007,0,1.812-0.823,1.812-1.831V6.113C30.5,5.104,29.695,4.282,28.688,4.282z M22.015,17.078
c-0.194-0.226-0.493-0.402-0.829-0.402c-0.328,0-0.562,0.156-0.829,0.37l-1.211,1.023c-0.252,0.182-0.452,0.303-0.744,0.303
c-0.278,0-0.529-0.102-0.712-0.263c-0.064-0.059-0.182-0.169-0.279-0.268l-3.483-3.768c-0.256-0.297-0.646-0.485-1.079-0.485
c-0.434,0-0.835,0.213-1.089,0.505l-8.187,9.88V7.11c0.065-0.441,0.409-0.758,0.847-0.758h23.154c0.448,0,0.81,0.329,0.837,0.776
l0.018,16.856L22.015,17.078z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

@@ -1,24 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
const SvgComponent = props => (
<svg
width={(props.scale || 1) * 0.75 * 32}
height={(props.scale || 1) * 0.75 * 32}
viewBox="0 0 32 32"
{...props}
>
<g fill={props.fill || '#FFF'}>
<path
scale={(props.scale || 1) * 0.75}
d="M16.041 12.217H16h.041zM25.082 8.342h-3.915s2.1 1.373 2.551 3.875H25.082a2.523 2.523 0 0 1 2.543 2.543v2.583c0 1.421-1.122 2.624-2.543 2.624H16.04c-1.421 0-2.624-1.203-2.624-2.624V14.8H9.542v2.543c0 .929.202 1.816.557 2.624 1.017 2.276 3.302 3.875 5.942 3.875h9.042c3.568 0 6.418-2.931 6.418-6.499V14.76a6.39 6.39 0 0 0-6.419-6.418z"
/>
<path
scale={(props.scale || 1) * 0.75}
d="M21.942 12.217a6.39 6.39 0 0 0-5.902-3.875H7c-3.569 0-6.5 2.85-6.5 6.418v2.583c0 3.568 2.931 6.499 6.5 6.499h3.833s-2.082-1.373-2.59-3.875H7c-1.421 0-2.624-1.203-2.624-2.624V14.76c0-1.421 1.203-2.543 2.624-2.543h9.041a2.523 2.523 0 0 1 2.543 2.543v2.623h3.875V14.76c0-.904-.186-1.768-.517-2.543z"
/>
</g>
</svg>
);
export default SvgComponent;
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
<g>
<path fill="#FFFFFF" d="M16.041,12.217H16l0,0C16.016,12.217,16.024,12.217,16.041,12.217z"/>
<path fill="#FFFFFF" d="M25.082,8.342h-3.915c0,0,2.1,1.373,2.551,3.875h1.323h0.041c1.421,0,2.543,1.123,2.543,2.543v2.583
c0,1.421-1.122,2.624-2.543,2.624h-9.042c-1.421,0-2.624-1.203-2.624-2.624V14.8H9.542v2.543c0,0.929,0.202,1.816,0.557,2.624
c1.017,2.276,3.302,3.875,5.942,3.875h9.042c3.568,0,6.418-2.931,6.418-6.499V14.76C31.5,11.192,28.65,8.342,25.082,8.342z"/>
<path fill="#FFFFFF" d="M21.942,12.217c-0.977-2.284-3.237-3.875-5.902-3.875H7c-3.569,0-6.5,2.85-6.5,6.418v2.583
c0,3.568,2.931,6.499,6.5,6.499h3.833c0,0-2.082-1.373-2.59-3.875H7c-1.421,0-2.624-1.203-2.624-2.624V14.76
c0-1.421,1.203-2.543,2.624-2.543h9h0.041c1.42,0,2.543,1.123,2.543,2.543v2.583c0,0.016,0,0.024,0,0.04h3.875
c0-0.016,0-0.024,0-0.04V14.76C22.459,13.856,22.273,12.992,21.942,12.217z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1,19 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
const SvgComponent = props => (
<svg
width={(props.scale || 1) * 0.75 * 32}
height={(props.scale || 1) * 0.75 * 32}
viewBox="0 0 32 32"
{...props}
>
<path
scale={(props.scale || 1) * 0.75}
fill={props.fill || '#FFF'}
d="M5.288 28c-.71 0-1.07-.371-1.25-.711-.224-.422-.272-.982-.137-1.58.152-.678.527-1.293 1.177-1.93.801-.781 1.974-1.564 3.305-2.207a26.563 26.563 0 0 1 2.272-.959c.978-1.523 2.363-3.907 3.36-6.527.108-.289.213-.574.312-.863-.307-.71-.498-1.32-.645-1.847-.276-.976-.451-1.854-.524-2.606a9.468 9.468 0 0 1 .039-2.184c.095-.714.259-1.224.51-1.601a2.217 2.217 0 0 1 1.595-.943l.07-.011a1.56 1.56 0 0 1 .766.033c.21.064.414.174.632.305l.005-.013c1.052.496.954 1.39.826 2.52-.018.143-.035.299-.053.471-.143 1.333-.546 3.496-1.259 5.761.049.098.101.199.154.3.535 1.006 1.31 2.164 2.244 3.351.428.539.918 1.117 1.428 1.693a17.197 17.197 0 0 1 2.426.014c.852.07 1.703.207 2.465.396.877.211 1.512.461 2.055.807.699.434 1.238 1.02 1.514 1.646.324.713.338 1.533.031 2.178-.188.404-.604.941-1.51 1.238a3.359 3.359 0 0 1-2 .01c-.641-.193-1.354-.572-2.188-1.16-.809-.574-1.576-1.234-2.486-2.139-.35-.348-.699-.713-1.051-1.084-.568.064-1.203.164-2.003.309-.944.172-2.109.402-3.589.838-.655.191-1.305.398-1.923.619-.078.115-.151.225-.219.328-1.272 1.885-2.513 3.361-3.591 4.279-.831.705-1.601 1.104-2.358 1.232a2.352 2.352 0 0 1-.4.037zm3.281-4.467c-1.186.643-1.881 1.236-2.211 1.559-.472.461-.606.764-.662.982.345-.125.731-.369 1.166-.736.408-.348.981-.91 1.707-1.805zm13.261-3.271c.781.77 1.445 1.338 2.137 1.826.822.58 1.354.811 1.65.9.324.096.619.098.92.004.117-.043.346-.135.412-.279.061-.131.074-.387-.043-.645-.137-.299-.432-.609-.818-.852-.373-.234-.84-.414-1.518-.576a14.937 14.937 0 0 0-2.176-.352c-.185-.011-.374-.024-.564-.026zm-6.357-4.88a36.788 36.788 0 0 1-2.141 4.342 35.008 35.008 0 0 1 3.707-.855c.316-.061.607-.111.883-.154a34.1 34.1 0 0 1-.671-.822 28.7 28.7 0 0 1-1.778-2.511zm.185-9.54l-.022.003c-.037.005-.073.011-.11.015-.065.009-.082.01-.122.025a.382.382 0 0 0-.181.129c-.033.05-.136.25-.21.818a7.788 7.788 0 0 0-.033 1.763c.042.428.124.911.246 1.441.261-1.139.423-2.148.502-2.883.018-.177.037-.337.054-.482.029-.265.061-.539.071-.717-.067-.039-.141-.083-.195-.112zm.328.16l.029.022.121-.267-.15.245z"
/>
</svg>
);
export default SvgComponent;
@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
<path fill="#FFFFFF" d="M5.288,28c-0.71,0-1.07-0.371-1.25-0.711c-0.224-0.422-0.272-0.982-0.137-1.58
c0.152-0.678,0.527-1.293,1.177-1.93c0.801-0.781,1.974-1.564,3.305-2.207c0.656-0.318,1.432-0.646,2.272-0.959
c0.978-1.523,2.363-3.907,3.36-6.527c0.108-0.289,0.213-0.574,0.312-0.863c-0.307-0.71-0.498-1.32-0.645-1.847
c-0.276-0.976-0.451-1.854-0.524-2.606c-0.071-0.74-0.058-1.475,0.039-2.184c0.095-0.714,0.259-1.224,0.51-1.601
c0.33-0.476,0.761-0.708,1.067-0.82c0.239-0.087,0.421-0.11,0.528-0.123l0.07-0.011c0.133-0.024,0.411-0.074,0.766,0.033
c0.21,0.064,0.414,0.174,0.632,0.305l0.005-0.013c1.052,0.496,0.954,1.39,0.826,2.52c-0.018,0.143-0.035,0.299-0.053,0.471
c-0.143,1.333-0.546,3.496-1.259,5.761c0.049,0.098,0.101,0.199,0.154,0.3c0.535,1.006,1.31,2.164,2.244,3.351
c0.428,0.539,0.918,1.117,1.428,1.693c0.822-0.053,1.641-0.047,2.426,0.014c0.852,0.07,1.703,0.207,2.465,0.396
c0.877,0.211,1.512,0.461,2.055,0.807c0.699,0.434,1.238,1.02,1.514,1.646c0.324,0.713,0.338,1.533,0.031,2.178
c-0.188,0.404-0.604,0.941-1.51,1.238c-0.65,0.207-1.322,0.209-2,0.01c-0.641-0.193-1.354-0.572-2.188-1.16
c-0.809-0.574-1.576-1.234-2.486-2.139c-0.35-0.348-0.699-0.713-1.051-1.084c-0.568,0.064-1.203,0.164-2.003,0.309
c-0.944,0.172-2.109,0.402-3.589,0.838c-0.655,0.191-1.305,0.398-1.923,0.619c-0.078,0.115-0.151,0.225-0.219,0.328
c-1.272,1.885-2.513,3.361-3.591,4.279c-0.831,0.705-1.601,1.104-2.358,1.232C5.543,27.988,5.411,28,5.288,28z M8.569,23.533
c-1.186,0.643-1.881,1.236-2.211,1.559c-0.472,0.461-0.606,0.764-0.662,0.982c0.345-0.125,0.731-0.369,1.166-0.736
C7.27,24.99,7.843,24.428,8.569,23.533z M21.83,20.262c0.781,0.77,1.445,1.338,2.137,1.826c0.822,0.58,1.354,0.811,1.65,0.9
c0.324,0.096,0.619,0.098,0.92,0.004c0.117-0.043,0.346-0.135,0.412-0.279c0.061-0.131,0.074-0.387-0.043-0.645
c-0.137-0.299-0.432-0.609-0.818-0.852c-0.373-0.234-0.84-0.414-1.518-0.576c-0.674-0.164-1.422-0.285-2.176-0.352
C22.209,20.277,22.02,20.264,21.83,20.262z M15.473,15.382c-0.655,1.602-1.424,3.094-2.141,4.342
c1.533-0.439,2.731-0.684,3.707-0.855c0.316-0.061,0.607-0.111,0.883-0.154c-0.236-0.281-0.459-0.553-0.671-0.822
C16.576,17.035,15.973,16.183,15.473,15.382z M15.658,5.842l-0.022,0.003c-0.037,0.005-0.073,0.011-0.11,0.015
c-0.065,0.009-0.082,0.01-0.122,0.025c-0.037,0.012-0.128,0.053-0.181,0.129c-0.033,0.05-0.136,0.25-0.21,0.818
c-0.078,0.563-0.088,1.172-0.033,1.763c0.042,0.428,0.124,0.911,0.246,1.441c0.261-1.139,0.423-2.148,0.502-2.883
c0.018-0.177,0.037-0.337,0.054-0.482c0.029-0.265,0.061-0.539,0.071-0.717C15.786,5.915,15.712,5.871,15.658,5.842z M15.986,6.002
c0.01,0.008,0.02,0.016,0.029,0.022l0.121-0.267L15.986,6.002z"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

@@ -1,25 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
const SvgComponent = props => (
<svg
width={(props.scale || 1) * 0.75 * 32}
height={(props.scale || 1) * 0.75 * 32}
viewBox="0 0 32 32"
{...props}
>
<g fill={props.fill || '#FFF'}>
<path scale={(props.scale || 1) * 0.75} d="M1.479 4.082h29.043V20.42H1.479z" />
<path
scale={(props.scale || 1) * 0.75}
d="M30.521 21.898H1.479A1.479 1.479 0 0 1 0 20.42V4.082a1.48 1.48 0 0 1 1.479-1.479h29.043c.816 0 1.478.662 1.478 1.479V20.42c0 .816-.662 1.478-1.479 1.478zM2.957 18.941h26.086V5.56H2.957v13.381z"
/>
<path
scale={(props.scale || 1) * 0.75}
d="M19.747 30.074c-.56 0-1.097-.32-1.345-.863l-2.513-5.484-3.019 5.572a1.478 1.478 0 1 1-2.6-1.408l4.429-8.176a1.466 1.466 0 0 1 1.349-.772c.56.019 1.062.353 1.294.862l3.746 8.174a1.48 1.48 0 0 1-1.341 2.095z"
/>
</g>
</svg>
);
export default SvgComponent;
@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
<g>
<g>
<rect x="1.479" y="4.082" fill="#FFFFFF" width="29.043" height="16.338"/>
<path fill="#FFFFFF" d="M30.521,21.898H1.479C0.662,21.898,0,21.236,0,20.42V4.082c0-0.816,0.662-1.479,1.479-1.479h29.043
C31.338,2.603,32,3.265,32,4.082V20.42C32,21.236,31.338,21.898,30.521,21.898z M2.957,18.941h26.086V5.56H2.957V18.941z"/>
</g>
<path fill="#FFFFFF" d="M19.747,30.074c-0.56,0-1.097-0.32-1.345-0.863l-2.513-5.484l-3.019,5.572
c-0.389,0.719-1.286,0.984-2.004,0.596s-0.985-1.285-0.596-2.004l4.429-8.176c0.267-0.492,0.785-0.795,1.349-0.772
c0.56,0.019,1.062,0.353,1.294,0.862l3.746,8.174c0.34,0.742,0.015,1.619-0.729,1.961C20.162,30.031,19.953,30.074,19.747,30.074z"
/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,19 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
const SvgComponent = props => (
<svg
width={(props.scale || 1) * 0.75 * 32}
height={(props.scale || 1) * 0.75 * 32}
viewBox="0 0 32 32"
{...props}
>
<path
scale={(props.scale || 1) * 0.75}
fill={props.fill || '#FFF'}
d="M18.802 3.5H16.67v17.38c-1.181-.449-2.707-.457-4.211.09-2.697.979-4.306 3.369-3.591 5.33.715 1.967 3.481 2.76 6.179 1.777 2.291-.832 3.791-2.68 3.75-4.422l.005-14.214c3.722.586 3.973 5.289 3.531 6.607-.171.5.126.873.688 0C27.032 9.81 18.802 7.057 18.802 3.5z"
/>
</svg>
);
export default SvgComponent;
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
<g>
<path fill="#FFFFFF" d="M18.802,3.5h-2.132v17.38c-1.181-0.449-2.707-0.457-4.211,0.09c-2.697,0.979-4.306,3.369-3.591,5.33
c0.715,1.967,3.481,2.76,6.179,1.777c2.291-0.832,3.791-2.68,3.75-4.422l0.005-14.214c3.722,0.586,3.973,5.289,3.531,6.607
c-0.171,0.5,0.126,0.873,0.688,0C27.032,9.81,18.802,7.057,18.802,3.5z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 755 B

@@ -1,28 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
const SvgComponent = props => (
<svg
width={(props.scale || 1) * 0.75 * 32}
height={(props.scale || 1) * 0.75 * 32}
{...props}
viewBox="0 0 32 32"
>
<g fill={props.fill || '#FFF'}>
<path
scale={(props.scale || 1) * 0.75}
d="M28.705 28.018H3.293A1.293 1.293 0 0 1 2 26.726V5.274c0-.713.58-1.292 1.293-1.292h25.412c.715 0 1.295.58 1.295 1.292v21.451c0 .715-.58 1.293-1.295 1.293zM4.587 25.431h22.827V6.569H4.587v18.862z"
/>
<path
scale={(props.scale || 1) * 0.75}
d="M28.705 20.864H3.293a1.293 1.293 0 0 1 0-2.585h25.412a1.293 1.293 0 1 1 0 2.585zM28.705 13.723H3.293a1.295 1.295 0 0 1 0-2.588h25.412a1.294 1.294 0 1 1 0 2.588z"
/>
<path
scale={(props.scale || 1) * 0.75}
d="M16 28.018a1.291 1.291 0 0 1-1.293-1.292V5.274a1.295 1.295 0 0 1 2.588 0v21.451c0 .715-.58 1.293-1.295 1.293z"
/>
</g>
</svg>
);
export default SvgComponent;
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
<g>
<path fill="#FFFFFF" d="M28.705,28.018H3.293C2.58,28.018,2,27.44,2,26.726V5.274c0-0.713,0.58-1.292,1.293-1.292h25.412
C29.42,3.982,30,4.562,30,5.274v21.451C30,27.44,29.42,28.018,28.705,28.018z M4.587,25.431h22.827V6.569H4.587V25.431z"/>
<path fill="#FFFFFF" d="M28.705,20.864H3.293C2.58,20.864,2,20.284,2,19.57c0-0.713,0.58-1.291,1.293-1.291h25.412
c0.715,0,1.295,0.578,1.295,1.291C30,20.284,29.42,20.864,28.705,20.864z"/>
<path fill="#FFFFFF" d="M28.705,13.723H3.293C2.58,13.723,2,13.143,2,12.428s0.58-1.293,1.293-1.293h25.412
c0.715,0,1.295,0.578,1.295,1.293S29.42,13.723,28.705,13.723z"/>
<path fill="#FFFFFF" d="M16,28.018c-0.715,0-1.293-0.577-1.293-1.292V5.274c0-0.713,0.578-1.292,1.293-1.292
s1.295,0.58,1.295,1.292v21.451C17.295,27.44,16.715,28.018,16,28.018z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

@@ -1,33 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
const SvgComponent = props => (
<svg
width={(props.scale || 1) * 0.75 * 32}
height={(props.scale || 1) * 0.75 * 32}
viewBox="0 0 32 32"
>
<path
scale={(props.scale || 1) * 0.75}
fill={props.fill || '#FFF'}
d="M20.591 29.731l-1.004-1.006a11.056 11.056 0 0 0 3.261-7.875c0-2.976-1.157-5.77-3.261-7.873a11.062 11.062 0 0 0-7.874-3.26 11.061 11.061 0 0 0-7.874 3.26l-1.005-1.005a12.482 12.482 0 0 1 8.879-3.676c3.353 0 6.509 1.309 8.877 3.676a12.479 12.479 0 0 1 3.678 8.878 12.48 12.48 0 0 1-3.677 8.881z"
/>
<path
scale={(props.scale || 1) * 0.75}
fill={props.fill || '#FFF'}
d="M2 11.376v2.559h2.559v-2.559zM18.819 28.006v2.557h2.557v-2.557zM29.452 21.085l-.603.603L10.752 3.606l.602-.603z"
/>
<path
scale={(props.scale || 1) * 0.75}
fill={props.fill || '#FFF'}
d="M18.819 11.044v2.559h2.557v-2.559z"
/>
<path
scale={(props.scale || 1) * 0.75}
fill={props.fill || '#FFF'}
d="M21.615 13.84h-3.03v-3.032h3.03v3.032zm-2.56-.475h2.086v-2.083h-2.086v2.083zM11.238 4.175c.475 0 .854-.38.854-.854s-.378-.854-.854-.854c-.473 0-.852.38-.852.854s.379.854.852.854zM29.146 22.179a.848.848 0 0 0 .854-.854.85.85 0 0 0-.854-.854.85.85 0 0 0-.854.854.851.851 0 0 0 .854.854z"
/>
</svg>
);
export default SvgComponent;
@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
<g>
<g>
<path fill="#FFFFFF" d="M20.591,29.731l-1.004-1.006c2.104-2.104,3.261-4.897,3.261-7.875c0-2.976-1.157-5.77-3.261-7.873
c-2.104-2.103-4.899-3.26-7.874-3.26c-2.977,0-5.771,1.158-7.874,3.26l-1.005-1.005c2.369-2.368,5.524-3.676,8.879-3.676
c3.353,0,6.509,1.309,8.877,3.676c2.369,2.369,3.678,5.524,3.678,8.878C24.269,24.207,22.96,27.359,20.591,29.731z"/>
</g>
<g>
<polygon fill="#FFFFFF" points="2,11.376 2,13.935 4.559,13.935 4.559,11.376 "/>
</g>
<g>
<polygon fill="#FFFFFF" points="18.819,28.006 18.819,30.563 21.376,30.563 21.376,28.006 "/>
</g>
<g>
<rect x="19.675" y="-0.446" transform="matrix(-0.7068 0.7074 -0.7074 -0.7068 43.0427 6.8514)" fill="#FFFFFF" width="0.853" height="25.583"/>
</g>
<g>
<polygon fill="#FFFFFF" points="18.819,11.044 18.819,13.603 21.376,13.603 21.376,11.044 "/>
</g>
<g>
<path fill="#FFFFFF" d="M21.615,13.84h-3.03v-3.032h3.03V13.84z M19.055,13.365h2.086v-2.083h-2.086V13.365z"/>
</g>
<g>
<path fill="#FFFFFF" d="M11.238,4.175c0.475,0,0.854-0.38,0.854-0.854s-0.378-0.854-0.854-0.854c-0.473,0-0.852,0.38-0.852,0.854
S10.765,4.175,11.238,4.175L11.238,4.175z"/>
</g>
<g>
<path fill="#FFFFFF" d="M29.146,22.179c0.476,0,0.854-0.378,0.854-0.854c0-0.475-0.378-0.854-0.854-0.854
c-0.474,0-0.854,0.379-0.854,0.854C28.293,21.801,28.673,22.179,29.146,22.179L29.146,22.179z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

@@ -1,19 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
const SvgComponent = props => (
<svg
width={(props.scale || 1) * 0.75 * 32}
height={(props.scale || 1) * 0.75 * 32}
viewBox="0 0 32 32"
{...props}
>
<path
scale={(props.scale || 1) * 0.75}
fill={props.fill || '#FFF'}
d="M6.465 4.002v24l20-12z"
/>
</svg>
);
export default SvgComponent;
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
<polygon fill="#FFFFFF" points="6.465,4.002 6.465,28.002 26.465,16.002 "/>
</svg>

Before

Width:  |  Height:  |  Size: 504 B

@@ -1,56 +0,0 @@
/* eslint-disable react/prop-types */
/* eslint-disable react/jsx-key */
import React from 'react';
import Icon from 'app/components/icon/icon.jsx';
import FolderIcon from '@material-ui/icons/FolderOutlined';
import Menu from 'components/menus/menu.jsx';
import Numbers from 'app/features/global/utils/Numbers';
import '../drive.scss';
import 'moment-timezone';
import { getCompanyApplication as getApplication } from 'app/features/applications/state/company-applications';
export default class Directory extends React.Component {
constructor() {
super();
}
render() {
var app = {};
if (this.props.data.application_id && this.props.data.external_storage) {
app = getApplication(this.props.data.application_id);
}
return [
<div style={{ display: 'inherit', width: '100%' }}>
<div className="icon">
{!app.id && <FolderIcon className="m-icon-small" />}
{!!app.id && (
<div
className="app_icon"
style={{ backgroundImage: "url('" + app.identity?.icon + "')" }}
/>
)}
</div>
<div className="text">
{this.props.data.name}
{!!app.id && <i> ({app.identity?.name})</i>}
</div>
{(this.props.data.acces_info || {}).token && (
<Icon type="cloud-share" className="m-icon-small" />
)}
{this.props.details && (
<div className="size no-grid">
{this.props.data.size ? Numbers.humanFileSize(this.props.data.size, true) : '-'}
</div>
)}
{this.props.menu && this.props.menu.length > 0 && (
<Menu menu={this.props.menu} className="options">
<Icon type="ellipsis-h" className="m-icon-small" />
</Menu>
)}
</div>,
];
}
}
@@ -1,63 +0,0 @@
/* eslint-disable react/prop-types */
import React, { Component } from 'react';
import Archive from '../icons/archive.jsx';
import Code from '../icons/code.jsx';
import Document from '../icons/document.jsx';
import Files from '../icons/file.jsx';
import Images from '../icons/image.jsx';
import Link from '../icons/link.jsx';
import Pdf from '../icons/pdf.jsx';
import Slide from '../icons/slides.jsx';
import Sound from '../icons/sound.jsx';
import Spreadsheet from '../icons/spreadsheet.jsx';
import Svg from '../icons/svg.jsx';
import Video from '../icons/video.jsx';
export default class FileType extends Component {
/* props : type (string) */
constructor() {
super();
}
render() {
var type = this.props.type;
var TypeIcon = Files;
switch (type) {
case 'link':
TypeIcon = Link;
break;
case 'code':
TypeIcon = Code;
break;
case 'document':
TypeIcon = Document;
break;
case 'image':
TypeIcon = Images;
break;
case 'pdf':
TypeIcon = Pdf;
break;
case 'slides':
TypeIcon = Slide;
break;
case 'audio':
TypeIcon = Sound;
break;
case 'spreadsheet':
TypeIcon = Spreadsheet;
break;
case 'svg':
TypeIcon = Svg;
break;
case 'video':
TypeIcon = Video;
break;
case 'archive':
TypeIcon = Archive;
break;
default:
}
return <TypeIcon {...this.props} />;
}
}
@@ -1,118 +0,0 @@
/* eslint-disable react/prop-types */
/* eslint-disable react/jsx-key */
import React from 'react';
import moment from 'moment';
import 'moment-timezone';
import Icon from 'app/components/icon/icon';
import Menu from 'components/menus/menu';
import DriveService from 'app/deprecated/Apps/Drive/Drive';
import Numbers from 'app/features/global/utils/Numbers';
import FileType from './file-type';
import TagPicker from 'components/tag-picker/tag-picker';
import { addApiUrlIfNeeded } from 'app/features/global/utils/URLUtils';
import '../drive.scss';
import { formatTime } from '@features/global/utils/Numbers';
export default class File extends React.Component {
constructor() {
super();
}
render() {
var date = false;
if (this.props.data.modified) {
date = new Date(this.props.data.modified * 1000);
}
if (this.props.data.added && this.props.isVersion) {
date = new Date(this.props.data.added * 1000);
}
var date_string = date ? formatTime(date) : '-';
return [
<div
key="preview"
className={
'preview file_type_icon ' +
DriveService.getFileType(this.props.data) +
' ' +
(this.props.data.has_preview ? '' : 'no_preview')
}
style={
this.props.data.has_preview
? { backgroundImage: addApiUrlIfNeeded(this.props.data.preview_link, true) }
: {}
}
>
<div className="tags_list">
<TagPicker inline readOnly noPlaceholder value={this.props.data.tags || []} />
</div>
</div>,
<div key="data" className="data">
<div className={'file_type_icon'}>
<FileType type={DriveService.getFileType(this.props.data)} scale={0.75} fill={'#000'} />
</div>
<div className="text">
{!!this.props.versionLabel && <div className="inline-tag">{this.props.versionLabel}</div>}
{this.props.data.name}
{!this.props.isVersion && (this.props.data.versions || {}).length > 1 && (
<span style={{ opacity: 0.5, marginLeft: 4 }}>
({(this.props.data.versions || {}).length} versions)
</span>
)}
&nbsp;&nbsp;
<TagPicker
className="no-grid"
inline
readOnly
noPlaceholder
value={this.props.data.tags || []}
/>
</div>
{(this.props.data.acces_info || {}).token && (
<Icon type="link-h" className="m-icon-small" />
)}
{this.props.data.url && <Icon type="external-link-alt" className="m-icon-small" />}
{/*
<div className="created-by no-grid">
{this.state.element.created_by || "-"}
</div>
*/}
{this.props.details && [
<div className="last-modified no-grid">{date_string}</div>,
<div className="size no-grid">
{this.props.data.size ? Numbers.humanFileSize(this.props.data.size, true) : '-'}
</div>,
]}
{!this.props.isVersion && (
<div className="detail_preview_parent no-grid">
<div
className={
'detail_preview file_type_icon ' +
DriveService.getFileType(this.props.data) +
' ' +
(this.props.data.has_preview ? '' : 'no_preview')
}
style={
this.props.data.has_preview
? { backgroundImage: addApiUrlIfNeeded(this.props.data.preview_link, true) }
: {}
}
/>
</div>
)}
{this.props.menu && this.props.menu.length > 0 && (
<Menu menu={this.props.menu} className="options">
<Icon type="ellipsis-h" className="m-icon-small" />
</Menu>
)}
{this.props.removeIcon === true && (
<Icon type="times" className="m-icon-small" onClick={this.props.removeOnClick} />
)}
</div>,
];
}
}
@@ -1,128 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
import Languages from 'app/features/global/services/languages-service';
import Icon from 'app/components/icon/icon.jsx';
import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js';
import Workspaces from 'app/deprecated/workspaces/workspaces.jsx';
import DriveService from 'app/deprecated/Apps/Drive/Drive.js';
import UploadZone from 'components/uploads/upload-zone';
import WorkspaceUserRights from 'app/features/workspaces/services/workspace-user-rights-service';
import MediumPopupManager from 'app/components/modal/modal-manager';
import { ObjectModal, ObjectModalTitle } from 'components/object-modal/deprecated-object-modal.jsx';
import UIFile from './ui/file.jsx';
import 'moment-timezone';
import './drive.scss';
export default class VersionDetails extends React.Component {
constructor() {
super();
this.state = {
workspaces: Workspaces,
i18n: Languages,
};
Languages.addListener(this);
DriveService.addListener(this);
Collections.get('drive').addListener(this);
}
componentWillUnmount() {
Languages.removeListener(this);
DriveService.removeListener(this);
Collections.get('drive').removeListener(this);
}
render() {
var countVersion = (this.props.file.versions || []).length;
return (
<ObjectModal
onClose={() => MediumPopupManager.closeAll()}
footer={
<div className="footerVersionDetails">
<div className="addVersion">
<div
className="addVersionButton"
onClick={() => {
if (this.upload_zone) {
this.upload_zone.open();
}
}}
>
<div className="icon">
<Icon type="plus" className="m-icon-small iconWithBackground" />
</div>
<div className="footerTitle">
{Languages.t('components.drive.new_versions', [], 'Ajouter une nouvelle version')}
</div>
</div>
</div>
</div>
}
title={
<div className="title allow_selection">
<ObjectModalTitle>
{Languages.t('scenes.apps.drive.right_preview.versions', [], 'Versions')}
</ObjectModalTitle>
</div>
}
>
<div className="versionDetails drive_view list">
<UploadZone
disabled={WorkspaceUserRights.isNotConnected()}
disableClick
ref={node => (this.upload_zone = node)}
parent={this.props.file.parent_id}
driveCollectionKey={{ id: this.props.file.parent_id }}
uploadOptions={{
workspace_id: this.state.workspaces.currentWorkspaceId,
new_version: true,
file_id: this.props.file.id,
}}
allowPaste={true}
>
{(this.props.file.versions || [])
.sort((a, b) => {
return b.added - a.added;
})
.map((version, index) => {
var data = {};
if (index > 0) {
data.extension = this.props.file.extension;
data.name = version.name;
data.creator = version.creator;
data.size = version.size;
} else {
data = JSON.parse(JSON.stringify(this.props.file));
}
data.added = version.added;
return (
<div className="file" key={'version-' + version.id}>
<UIFile
isVersion
versionLabel={
index == 0 ? 'Version actuelle' : 'Version ' + (countVersion - index)
}
data={data}
menu={[
{
type: 'menu',
text: Languages.t(
'scenes.apps.drive.preview_bloc.operations_download',
[],
'Télécharger',
),
onClick: () => {
var link = DriveService.getLink(this.props.file, version.id, 1);
window.open(link);
},
},
]}
details={true}
/>
</div>
);
})}
</UploadZone>
</div>
</ObjectModal>
);
}
}
@@ -1,101 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Button } from 'app/atoms/button/button';
import { BaseSmall } from 'app/atoms/text';
import Switch from 'app/components/inputs/switch';
import { ChannelType } from 'app/features/channels/types/channel';
import Languages from 'app/features/global/services/languages-service';
import Block from 'app/molecules/grouped-rows/base';
import { useState } from 'react';
export const ChannelAccessForm = (props: {
channel?: ChannelType;
onChange: (change: {
visibility: 'private' | 'public';
is_default: boolean;
is_readonly: boolean;
}) => void;
}) => {
const [visibility, setVisibility] = useState<'private' | 'public'>(
props.channel?.visibility || ('private' as any),
);
const [isDefault, setIsDefault] = useState(props.channel?.is_default || false);
const [isReadOnly, setIsReadOnly] = useState(props.channel?.is_readonly || false);
const [loading, setLoading] = useState(false);
return (
<div className="w-screen max-w-sm p-4 -m-4">
<Block
className="my-4"
avatar={<></>}
title={Languages.t('scenes.app.channelsbar.channel_access.visibility')}
subtitle={
<BaseSmall className="whitespace-normal leading-3">
{Languages.t('scenes.app.channelsbar.channel_access.visibility.info')}
</BaseSmall>
}
suffix={
<Switch
checked={visibility === 'public'}
onChange={() => {
if (visibility === 'public') setIsDefault(false);
setVisibility(visibility === 'public' ? 'private' : 'public');
}}
/>
}
/>
<Block
className="my-4"
avatar={<></>}
title={Languages.t('scenes.app.channelsbar.channel_access.default')}
subtitle={
<BaseSmall className="whitespace-normal leading-3">
{Languages.t('scenes.app.channelsbar.channel_access.default.info')}
</BaseSmall>
}
suffix={
<Switch
checked={isDefault}
onChange={() => {
setIsDefault(!isDefault);
if (visibility === 'private' && !isDefault) setVisibility('public');
}}
/>
}
/>
<Block
className="my-4"
avatar={<></>}
title={Languages.t('scenes.app.channelsbar.channel_access.readonly')}
subtitle={
<BaseSmall className="whitespace-normal leading-3">
{Languages.t('scenes.app.channelsbar.channel_access.readonly.info')}
</BaseSmall>
}
suffix={
<Switch
checked={isReadOnly}
onChange={() => {
setIsReadOnly(!isReadOnly);
}}
/>
}
/>
<div className="text-center mt-4">
<Button
theme="primary"
loading={loading}
onClick={() => {
setLoading(true);
props.onChange({ visibility, is_default: isDefault, is_readonly: isReadOnly });
setLoading(false);
}}
>
{Languages.t('scenes.app.channelsbar.channel_access.save')}
</Button>
</div>
</div>
);
};
@@ -1,248 +0,0 @@
import Avatar from 'app/atoms/avatar';
import { Button } from 'app/atoms/button/button';
import { Checkbox } from 'app/atoms/input/input-checkbox';
import { InputLabel } from 'app/atoms/input/input-decoration-label';
import Select from 'app/atoms/input/input-select';
import { Input } from 'app/atoms/input/input-text';
import A from 'app/atoms/link';
import { Modal, ModalContent } from 'app/atoms/modal';
import { usePublicOrPrivateChannels } from 'app/features/channels/hooks/use-public-or-private-channels';
import { ChannelType } from 'app/features/channels/types/channel';
import Languages from 'app/features/global/services/languages-service';
import { getBase64 } from 'app/features/global/utils/strings';
import Block from 'app/molecules/grouped-rows/base';
import _ from 'lodash';
import { useEffect, useState } from 'react';
import Emojione from '../emojione/emojione';
const ChannelGroupSelector = (props: { group: string; onChange: (str: string) => void }) => {
const clean = (str?: string) => str?.toLocaleLowerCase().trim().replace(/ +/, ' ');
const [group, setGroup] = useState<string>(clean(props.group) || '');
const [newGroup, setNewGroup] = useState<string>('');
const { publicChannels, privateChannels } = usePublicOrPrivateChannels();
const groups = _.uniq(
[...publicChannels, ...privateChannels]
.filter(p => p.channel_group)
.map(p => clean(p.channel_group) || ''),
).sort();
useEffect(() => {
if (!groups.includes(group) && !newGroup) setNewGroup(group);
}, [groups]);
return (
<div className="w-screen max-w-sm p-4 -m-4">
<hr className="my-1 -mx-4 mb-3" />
{groups.map(g => (
<Block
key={g}
className="my-3"
avatar={<Avatar nogradient title={g} size="sm" />}
title={_.capitalize(g)}
suffix={
<Checkbox
onChange={v => {
setGroup(v ? g : '');
}}
value={g === group}
/>
}
subtitle={<></>}
/>
))}
<Block
avatar={<div className="w-7" />}
title={
<div className="grow p-1 font-medium">
<Input
theme="outline"
onChange={e => {
setNewGroup(e.target.value);
setGroup(clean(e.target.value) || '');
}}
value={newGroup}
className=""
placeholder={Languages.t('scenes.app.channelsbar.channel_information.group.new')}
/>
</div>
}
suffix={
<Checkbox
disabled={!newGroup}
onChange={v => {
setGroup(v ? newGroup : '');
}}
value={!!group && clean(newGroup) === clean(group)}
/>
}
subtitle={<></>}
/>
<div className="text-center mt-4">
<Button
theme={group ? 'primary' : 'outline'}
className="max-w-xs"
onClick={() => props.onChange(group)}
>
<span className="text-ellipsis overflow-hidden whitespace-nowrap">
{group
? Languages.t('scenes.app.channelsbar.channel_information.group.save', [
_.capitalize(clean(group)),
])
: Languages.t('scenes.app.channelsbar.channel_information.group.save_none')}
</span>
</Button>
</div>
</div>
);
};
export const ChannelInformationForm = (props: {
channel?: ChannelType;
onChange: (change: {
name: string;
icon: string;
description: string;
channel_group: string;
}) => void;
}) => {
const [channelGroupModal, setChannelGroupModal] = useState<string | false>(false);
const [group, setGroup] = useState<string>(props.channel?.channel_group || '');
const [name, setName] = useState(props.channel?.name || '');
const [description, setDescription] = useState(props.channel?.description || '');
const [icon, setIcon] = useState(props.channel?.icon || '');
return (
<div className="w-screen max-w-sm p-4 -m-4">
<Modal open={channelGroupModal !== false} onClose={() => setChannelGroupModal(false)}>
<ModalContent title={Languages.t('scenes.app.channelsbar.channel_information.group.title')}>
<ChannelGroupSelector
group={channelGroupModal || ''}
onChange={group => {
setChannelGroupModal(false);
setGroup(group);
}}
/>
</ModalContent>
</Modal>
<div className="text-center my-4">
<div className="inline-block overflow-hidden m-0 p-0">
<Avatar
size="xl"
nogradient={!!(icon && (icon?.length || 0) < 20)}
icon={icon && (icon?.length || 0) < 20 ? <Emojione type={icon} /> : false}
avatar={(icon?.length || 0) > 20 ? icon : ''}
title={name}
/>
</div>
<div className="mt-2">
{!!icon && (
<A
className="!text-rose-500"
onClick={() => {
setIcon('');
}}
>
{Languages.t('scenes.app.channelsbar.channel_information.logo.remove')}
</A>
)}
{!icon && (
<A
onClick={() => {
const input = document.createElement('input');
input.style.position = 'absolute';
input.style.left = '-10000px';
input.type = 'file';
input.accept = 'image/png, image/jpeg, image/gif, image/webp';
input.multiple = false;
input.onchange = async () => {
if (input.files?.[0]) {
const b64 = await getBase64(input.files?.[0]);
setIcon(b64);
}
input.parentElement?.removeChild(input);
};
document.body.appendChild(input);
input.click();
}}
>
{Languages.t('scenes.app.channelsbar.channel_information.logo.add')}
</A>
)}
</div>
</div>
<InputLabel
label={Languages.t('scenes.app.channelsbar.channel_information.name')}
input={
<Input
className="int-channel-name"
theme="outline"
placeholder={Languages.t('scenes.app.channelsbar.channel_information.name')}
value={name}
onChange={e => setName(e.target.value)}
/>
}
/>
<InputLabel
className="mt-4"
label={Languages.t('scenes.app.channelsbar.channel_information.description')}
input={
<Input
className="int-channel-description"
theme="outline"
placeholder={Languages.t(
'scenes.app.channelsbar.channel_information.description.placeholder',
)}
defaultValue={description}
onChange={e => setDescription(e.target.value)}
multiline
/>
}
/>
<InputLabel
className="mt-4"
label={Languages.t('scenes.app.channelsbar.channel_information.group')}
input={
<div
className="cursor-pointer"
onClick={e => {
e.preventDefault();
e.stopPropagation();
setChannelGroupModal(group || '');
}}
>
<Select className="pointer-events-none" theme="outline">
{!group && (
<option disabled selected>
{Languages.t('scenes.app.channelsbar.channel_information.group.none')}
</option>
)}
{group && <option>{_.capitalize(group)}</option>}
</Select>
</div>
}
/>
<div className="text-center mt-6">
<Button
className="int-channel-save-information"
theme="primary"
onClick={() => {
props.onChange({ channel_group: group, name, description, icon });
}}
disabled={!name.trim()}
>
{Languages.t('scenes.app.channelsbar.channel_information.save')}
</Button>
</div>
</div>
);
};
@@ -1,89 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Checkbox } from 'app/atoms/input/input-checkbox';
import { useChannelMemberCurrentUser } from 'app/features/channel-members-search/hooks/member-hook';
import { ChannelType } from 'app/features/channels/types/channel';
import Languages from 'app/features/global/services/languages-service';
import { ToasterService } from 'app/features/global/services/toaster-service';
import Block from 'app/molecules/grouped-rows/base';
import { useState } from 'react';
export const ChannelNotificationsForm = (props: { channel?: ChannelType; onBack: () => void }) => {
const { member, setNotificationPreference } = useChannelMemberCurrentUser(
props.channel?.id || '',
);
const [value, _setValue] = useState(member?.notification_level || 'all');
const setValue = (value: any) => {
_setValue(value);
setNotificationPreference(value).finally(() => ToasterService.success('Updated'));
props.onBack();
};
return (
<div className="w-screen max-w-sm p-4 -m-4">
<Block
className="my-4"
avatar={<></>}
title={Languages.t('scenes.apps.messages.left_bar.stream.notifications.all')}
subtitle={<></>}
suffix={
<Checkbox
value={value === 'all'}
onChange={() => {
setValue('all');
}}
/>
}
/>
<Block
className="my-4"
avatar={<></>}
title={Languages.t('scenes.apps.messages.left_bar.stream.notifications.mentions', [
'@all',
'@here',
`@[you]`,
])}
subtitle={<></>}
suffix={
<Checkbox
value={value === 'mentions'}
onChange={() => {
setValue('mentions');
}}
/>
}
/>
<Block
className="my-4"
avatar={<></>}
title={Languages.t('scenes.apps.messages.left_bar.stream.notifications.me', [`@[you]`])}
subtitle={<></>}
suffix={
<Checkbox
value={value === 'me'}
onChange={() => {
setValue('me');
}}
/>
}
/>
<Block
className="my-4"
avatar={<></>}
title={Languages.t('scenes.apps.messages.left_bar.stream.notifications.never')}
subtitle={<></>}
suffix={
<Checkbox
value={value === 'none'}
onChange={() => {
setValue('none');
}}
/>
}
/>
</div>
);
};
@@ -1,368 +0,0 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/ban-types */
import {
BellIcon,
ChevronRightIcon,
HandIcon,
LinkIcon,
LogoutIcon,
PhotographIcon,
StarIcon,
TrashIcon,
} from '@heroicons/react/outline';
import { StarIcon as StarIconSolid } from '@heroicons/react/solid';
import Avatar from 'app/atoms/avatar';
import { UsersIcon } from 'app/atoms/icons-agnostic';
import A from 'app/atoms/link';
import { Loader } from 'app/atoms/loader';
import { Base, Info } from 'app/atoms/text';
import { useChannelMemberCurrentUser } from 'app/features/channel-members-search/hooks/member-hook';
import ChannelsMineAPIClient from 'app/features/channels/api/channels-mine-api-client';
import { useFavoriteChannels } from 'app/features/channels/hooks/use-favorite-channels';
import { ChannelType } from 'app/features/channels/types/channel';
import { isDirectChannel, isPrivateChannel } from 'app/features/channels/utils/utils';
import AlertManager from 'app/features/global/services/alert-manager-service';
import Languages from 'app/features/global/services/languages-service';
import { ToasterService } from 'app/features/global/services/toaster-service';
import { copyToClipboard } from 'app/features/global/utils/CopyClipboard';
import RouterServices from 'app/features/router/services/router-service';
import { useCurrentUser } from 'app/features/users/hooks/use-current-user';
import workspaceUserRightsService from 'app/features/workspaces/services/workspace-user-rights-service';
import Block from 'app/molecules/grouped-rows/base';
import { addUrlTryDesktop } from 'app/components/desktop-redirect';
import { useState } from 'react';
export const ChannelSettingsMenu = (props: {
channel?: ChannelType;
onEditChannel?: Function;
onAccess?: Function;
onMedias?: Function;
onNotifications?: Function;
onMembers?: Function;
onFavorite?: Function;
onClose?: Function;
}) => {
const icon = props.channel?.icon || '';
const name = props.channel?.name || '';
const { user } = useCurrentUser();
const canEdit =
props.channel?.owner === user?.id || workspaceUserRightsService.hasWorkspacePrivilege();
const isGuest = workspaceUserRightsService.isInvite();
const isDirect = isDirectChannel(props.channel?.visibility || '');
const canLeave = !isGuest || isDirect;
const canRemove =
user?.id === props.channel?.owner || workspaceUserRightsService.hasWorkspacePrivilege();
return (
<div className="w-screen max-w-sm p-4 -m-4">
{!isDirect && (
<>
<Block
onClick={() => {
canEdit && props.onEditChannel && props.onEditChannel();
}}
className={
'-mx-2 my-2 p-2 rounded-md ' +
(canEdit ? 'cursor-pointer hover:bg-blue-50 dark:hover:bg-zinc-800' : '')
}
title={<span className="capitalize">{name || ''}</span>}
subtitle={
canEdit ? (
<Info>
<A>{Languages.t('scenes.app.channelsbar.channel_information')}</A>
</Info>
) : (
<></>
)
}
avatar={<Avatar size="lg" avatar={icon.length > 20 ? icon : ''} title={name || ''} />}
suffix={canEdit ? <ChevronRightIcon className="w-5 h-5 text-zinc-500" /> : <></>}
/>
{canRemove && <RemoveBlock channel={props.channel} onLeave={props.onClose} />}
<hr className="my-2 -mx-4" />
<Block
onClick={() => {
canEdit && props.onAccess && props.onAccess();
}}
className={
'-mx-2 my-1 p-2 rounded-md ' +
(canEdit ? 'cursor-pointer hover:bg-blue-50 dark:hover:bg-zinc-800' : '')
}
title={
<Base className="font-semibold">
{Languages.t('scenes.app.channelsbar.channel_access')}
</Base>
}
subtitle={<></>}
avatar={<HandIcon className="text-blue-500 w-6 h-6" />}
suffix={
<div className="flex flex-row items-center justify-center">
<Info className="block mr-2">
{props.channel?.visibility === 'public' ? 'Public' : 'Private'}
</Info>
{canEdit ? <ChevronRightIcon className="w-5 h-5 text-zinc-500" /> : <> </>}
</div>
}
/>
{!isGuest && !!props.onMembers && (
<Block
onClick={() => {
props.onMembers && props.onMembers();
}}
className={
'-mx-2 my-1 p-2 rounded-md ' +
(canEdit ? 'cursor-pointer hover:bg-blue-50 dark:hover:bg-zinc-800' : '')
}
title={
<Base className="font-semibold">
{Languages.t('scenes.apps.parameters.workspace_sections.members')}
</Base>
}
subtitle={<></>}
avatar={<UsersIcon className="text-blue-500 w-6 h-6" />}
suffix={<ChevronRightIcon className="w-5 h-5 text-zinc-500" />}
/>
)}
</>
)}
<Block
onClick={() => {
props.onMedias && props.onMedias();
}}
className="-mx-2 my-1 p-2 rounded-md cursor-pointer hover:bg-blue-50 dark:hover:bg-zinc-800"
title={
<Base className="font-semibold">
{Languages.t('components.channel_attachement_list.title')}
</Base>
}
subtitle={<></>}
avatar={<PhotographIcon className="text-blue-500 w-6 h-6" />}
suffix={<ChevronRightIcon className="w-5 h-5 text-zinc-500" />}
/>
<hr className="my-2 -mx-4" />
<Block
onClick={() => {
const url = addUrlTryDesktop(
`${document.location.origin}${RouterServices.generateRouteFromState({
workspaceId: props.channel?.workspace_id || '',
companyId: props.channel?.company_id,
channelId: props.channel?.id,
})}`,
);
copyToClipboard(url);
ToasterService.success(Languages.t('components.input.copied'));
}}
className={
'-mx-2 my-1 p-2 rounded-md cursor-pointer hover:bg-blue-50 dark:hover:bg-zinc-800'
}
title={
<Base className="font-semibold">
{Languages.t('scenes.app.channelsbar.channel_copy_link')}
</Base>
}
subtitle={<></>}
avatar={<LinkIcon className="text-blue-500 w-6 h-6" />}
suffix={<></>}
/>
<FavoriteBlock channel={props.channel} />
{!isDirect && (
<NotificationsBlock
channel={props.channel}
onClick={() => {
props.onNotifications && props.onNotifications();
}}
/>
)}
{canLeave && <LeaveBlock channel={props.channel} onLeave={props.onClose} />}
<div className="-mb-2" />
</div>
);
};
const RemoveBlock = (props: { channel?: ChannelType; onLeave?: Function }) => {
const { refresh: refreshAllChannels } = useFavoriteChannels();
const [loading, setLoading] = useState(false);
return (
<Block
onClick={async () => {
AlertManager.confirm(async () => {
setLoading(true);
await ChannelsMineAPIClient.removeChannel(
props.channel?.company_id || '',
props.channel?.workspace_id || '',
props.channel?.id || '',
);
await refreshAllChannels();
RouterServices.push(
RouterServices.generateRouteFromState({
companyId: props.channel?.company_id || '',
workspaceId: props.channel?.workspace_id || '',
channelId: '',
}),
);
props.onLeave && props.onLeave();
});
}}
className="-mx-2 my-1 p-2 rounded-md cursor-pointer hover:bg-rose-50 dark:hover:bg-rose-800"
title={
<Base noColor className="font-semibold text-rose-500">
{Languages.t('scenes.app.channelsbar.channel_removing')}
</Base>
}
subtitle={<></>}
avatar={
loading ? (
<Loader className="w-4 h-4 m-1" />
) : (
<TrashIcon className="text-rose-500 w-6 h-6" />
)
}
/>
);
};
const LeaveBlock = (props: { channel?: ChannelType; onLeave?: Function }) => {
const { refresh: refreshAllChannels } = useFavoriteChannels();
const { user } = useCurrentUser();
const [loading, setLoading] = useState(false);
const leaveChannel = async (isDirectChannel = false) => {
setLoading(true);
if (props.channel?.id && props.channel?.company_id && props.channel.workspace_id) {
const res = await ChannelsMineAPIClient.removeUser(user?.id || '', {
companyId: props.channel.company_id,
workspaceId: isDirectChannel ? 'direct' : props.channel.workspace_id,
channelId: props.channel.id,
});
if (res?.error?.length && res?.message?.length) {
ToasterService.error(`${res.error} - ${res.message}`);
} else {
await refreshAllChannels();
RouterServices.push(
RouterServices.generateRouteFromState({
companyId: props.channel?.company_id || '',
workspaceId: props.channel?.workspace_id || '',
channelId: '',
}),
);
props.onLeave && props.onLeave();
}
await refreshAllChannels();
}
setLoading(false);
};
return (
<Block
onClick={async () => {
if (props.channel!.visibility) {
if (isPrivateChannel(props.channel!.visibility)) {
return AlertManager.confirm(() => leaveChannel(), undefined, {
title: Languages.t('components.alert.leave_private_channel.title'),
text: Languages.t('components.alert.leave_private_channel.description'),
});
}
if (isDirectChannel(props.channel!.visibility)) {
return leaveChannel(true);
}
}
return leaveChannel();
}}
className="-mx-2 my-1 p-2 rounded-md cursor-pointer hover:bg-rose-50 dark:hover:bg-rose-800"
title={
<Base noColor className="font-semibold text-rose-500">
{Languages.t('scenes.app.channelsbar.channel_leaving')}
</Base>
}
subtitle={<></>}
avatar={
loading ? (
<Loader className="w-4 h-4 m-1" />
) : (
<LogoutIcon className="text-rose-500 w-6 h-6" />
)
}
/>
);
};
const NotificationsBlock = (props: { channel?: ChannelType; onClick: Function }) => {
const { member } = useChannelMemberCurrentUser(props.channel?.id || '');
return (
<Block
onClick={() => {
props.onClick();
}}
className="-mx-2 my-1 p-2 rounded-md cursor-pointer hover:bg-blue-50 dark:hover:bg-zinc-800"
title={
<Base className="font-semibold">
{Languages.t('scenes.app.channelsbar.currentuser.user_parameter')}
</Base>
}
subtitle={<></>}
avatar={<BellIcon className="text-blue-500 w-6 h-6" />}
suffix={
<div className="flex flex-row items-center justify-center">
<Info className="block mr-2">
{member?.notification_level === 'all' &&
Languages.t('scenes.apps.messages.left_bar.stream.notifications.all')}
{member?.notification_level === 'mentions' && '@all, @[you]'}
{member?.notification_level === 'me' &&
Languages.t('scenes.apps.messages.left_bar.stream.notifications.me', [`@[you]`])}
{member?.notification_level === 'none' &&
Languages.t('scenes.apps.messages.left_bar.stream.notifications.never')}
</Info>
<ChevronRightIcon className="w-5 h-5 text-zinc-500" />
</div>
}
/>
);
};
const FavoriteBlock = (props: { channel?: ChannelType }) => {
const { favorite, setFavorite } = useChannelMemberCurrentUser(props.channel?.id || '');
const [loading, setLoading] = useState(false);
return (
<Block
onClick={async () => {
setLoading(true);
await setFavorite(!favorite);
setLoading(false);
}}
className="-mx-2 my-1 p-2 rounded-md cursor-pointer hover:bg-blue-50 dark:hover:bg-zinc-800"
title={
<Base className="font-semibold">
{Languages.t(
favorite
? 'scenes.apps.messages.left_bar.stream.remove_from_favorites'
: 'scenes.apps.messages.left_bar.stream.add_to_favorites',
)}
</Base>
}
subtitle={<></>}
avatar={
loading ? (
<Loader className="text-blue-500 w-4 h-4 m-1" />
) : favorite ? (
<StarIconSolid className="text-yellow-500 w-6 h-6" />
) : (
<StarIcon className="text-blue-500 w-6 h-6" />
)
}
/>
);
};
@@ -1,261 +0,0 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Transition } from '@headlessui/react';
import { ChevronLeftIcon } from '@heroicons/react/outline';
import A from 'app/atoms/link';
import { Loader } from 'app/atoms/loader';
import { Modal, ModalContent } from 'app/atoms/modal';
import { useUsersSearchModal } from 'app/features/channel-members-search/state/search-channel-member';
import ChannelAPIClient from 'app/features/channels/api/channel-api-client';
import { useChannel } from 'app/features/channels/hooks/use-channel';
import { useFavoriteChannels } from 'app/features/channels/hooks/use-favorite-channels';
import { channelAttachmentListState } from 'app/features/channels/state/channel-attachment-list';
import { ChannelType } from 'app/features/channels/types/channel';
import useRouteState from 'app/features/router/hooks/use-route-state';
import { useEffect, useState } from 'react';
import { atom, useRecoilState, useSetRecoilState } from 'recoil';
import { slideXTransition, slideXTransitionReverted } from 'src/utils/transitions';
import { ChannelAccessForm } from './channel-access';
import { ChannelInformationForm } from './channel-information';
import { ChannelNotificationsForm } from './channel-notifications';
import { ChannelSettingsMenu } from './channel-settings-menu';
import RouterServices from '@features/router/services/router-service';
import Languages from 'app/features/global/services/languages-service';
const EditChannelModalAtom = atom({
key: 'EditChannelModalAtom',
default: {
open: false,
channelId: '',
},
});
export const useOpenChannelModal = () => {
const setModal = useSetRecoilState(EditChannelModalAtom);
return (channelId: string) => setModal({ open: true, channelId });
};
export const EditChannelModal = () => {
const [channelModal, setChannelModal] = useRecoilState(EditChannelModalAtom);
return (
<Modal
open={channelModal.open}
onClose={() => setChannelModal({ ...channelModal, open: false })}
className="int-channel-edit-modal !max-w-sm"
>
{!channelModal.channelId && <CreateChannelForm />}
{!!channelModal.channelId && <EditChannelForm />}
</Modal>
);
};
const EditChannelForm = () => {
const [channelModal, setChannelModal] = useRecoilState(EditChannelModalAtom);
const [page, setPage] = useState<'information' | 'access' | 'notifications' | 'menu'>('menu');
const { setOpen: setParticipantsOpen } = useUsersSearchModal();
const [, setChannelAttachmentState] = useRecoilState(channelAttachmentListState);
const { refresh } = useFavoriteChannels();
const { channel } = useChannel(channelModal.channelId);
return (
<ModalContent
title={
<div className="flex flex-row items-center justify-start">
{page !== 'menu' && (
<A onClick={() => setPage('menu')}>
<ChevronLeftIcon className="w-6 h-6" />
</A>
)}
<span className="ml-2">{Languages.t('scenes.app.channelsbar.modify_channel_menu')}</span>
</div>
}
>
<hr className="my-1 -mx-4" />
<div
style={{
display: 'grid',
gridTemplate: '1fr / 1fr',
}}
>
<Transition
style={{
gridColumn: '1 / 1',
gridRow: '1 / 1',
}}
show={page === 'menu'}
as="div"
{...(page === 'menu' ? slideXTransitionReverted : slideXTransition)}
>
<ChannelSettingsMenu
onEditChannel={() => setPage('information')}
onAccess={() => setPage('access')}
onNotifications={() => setPage('notifications')}
onClose={() => {
setChannelModal({ ...channelModal, open: false });
}}
onMembers={() => {
setChannelModal({ ...channelModal, open: false });
setTimeout(() => setParticipantsOpen(true), 500);
}}
onMedias={() => {
setChannelModal({ ...channelModal, open: false });
setTimeout(() => setChannelAttachmentState(true), 500);
}}
channel={channel}
/>
</Transition>
<Transition
style={{
gridColumn: '1 / 1',
gridRow: '1 / 1',
}}
show={page === 'information'}
as="div"
{...(page === 'menu' ? slideXTransitionReverted : slideXTransition)}
>
<ChannelInformationForm
onChange={async changes => {
setPage('menu');
await ChannelAPIClient.save(
{ ...channel, ...changes },
{
companyId: channel.company_id!,
workspaceId: channel.workspace_id!,
channelId: channel.id,
},
);
await refresh();
}}
channel={channel}
/>
</Transition>
<Transition
style={{
gridColumn: '1 / 1',
gridRow: '1 / 1',
}}
show={page === 'access'}
as="div"
{...(page === 'menu' ? slideXTransitionReverted : slideXTransition)}
>
<ChannelAccessForm
onChange={async changes => {
await ChannelAPIClient.save(
{ ...channel, ...changes },
{
companyId: channel.company_id!,
workspaceId: channel.workspace_id!,
channelId: channel.id,
},
);
await refresh();
setPage('menu');
}}
channel={channel}
/>
</Transition>
<Transition
style={{
gridColumn: '1 / 1',
gridRow: '1 / 1',
}}
show={page === 'notifications'}
as="div"
{...(page === 'menu' ? slideXTransitionReverted : slideXTransition)}
>
<ChannelNotificationsForm onBack={() => setPage('menu')} channel={channel} />
</Transition>
</div>
</ModalContent>
);
};
const CreateChannelForm = () => {
const setChannelModal = useSetRecoilState(EditChannelModalAtom);
const { setOpen: setParticipantsOpen } = useUsersSearchModal();
const { companyId, workspaceId } = useRouteState();
const { refresh } = useFavoriteChannels();
const [step, setStep] = useState(0);
const [channel, setChannel] = useState<Partial<ChannelType>>({});
useEffect(() => {
if (step === 2) {
(async () => {
const created = await ChannelAPIClient.save(channel, {
companyId: companyId!,
workspaceId: workspaceId!,
});
await refresh();
RouterServices.push(RouterServices.generateRouteFromState({ channelId: created.id }));
setChannelModal({ open: false, channelId: '' });
setTimeout(() => {
setParticipantsOpen(true);
}, 500);
})();
}
}, [step]);
return (
<ModalContent title={Languages.t('scenes.app.channelsbar.channelsworkspace.create_channel')}>
<hr className="my-1 -mx-4" />
<div
style={{
display: 'grid',
gridTemplate: '1fr / 1fr',
}}
>
<Transition
style={{
gridColumn: '1 / 1',
gridRow: '1 / 1',
}}
show={step === 0}
as="div"
{...slideXTransition}
>
<ChannelInformationForm
onChange={changes => {
setStep(1);
setChannel({ ...channel, ...changes });
}}
/>
</Transition>
<Transition
style={{
gridColumn: '1 / 1',
gridRow: '1 / 1',
}}
show={step === 1}
as="div"
{...slideXTransition}
>
<ChannelAccessForm
onChange={async changes => {
setStep(2);
setChannel({ ...channel, ...changes });
}}
/>
</Transition>
<Transition
style={{
gridColumn: '1 / 1',
gridRow: '1 / 1',
}}
show={step === 2}
as="div"
{...slideXTransition}
>
<div className="flex items-center justify-center w-screen max-w-md h-screen max-h-96">
<Loader className="w-6 h-6" />
</div>
</Transition>
</div>
</ModalContent>
);
};
@@ -1,26 +0,0 @@
import React, { Component } from 'react';
import './editable-text.scss';
import Input from 'components/inputs/input.jsx';
export default class EditableText extends React.Component {
constructor(props) {
super();
this.props = props;
this.state = {
value: '#FF0000',
};
}
render() {
return (
<div className="color_input big">
<Input
value={this.state.value}
onChange={evt => this.setState({ value: evt.target.value })}
/>
<div className="color_square" style={{ backgroundColor: this.state.value }} />
</div>
);
}
}
@@ -1,19 +0,0 @@
.color_input {
position: relative;
.color_square {
position: absolute;
left: 8px;
top: 8px;
width: 24px;
height: 24px;
border-radius: var(--border-radius-base);
}
input {
background: #888;
padding-left: 40px !important;
box-sizing: border-box;
width: 120px;
}
}
@@ -1,142 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { Picker } from 'emoji-mart';
import Emojione from 'components/emojione/emojione';
import { getAsFrontUrl } from 'app/features/global/utils/URLUtils';
import './emoji-picker.scss';
import 'emoji-mart/css/emoji-mart.css';
import Languages from 'app/features/global/services/languages-service';
import { isArray } from 'lodash';
Picker.defaultProps.backgroundImageFn = function backgroundImageFn(set, sheetSize) {
sheetSize = 20;
return getAsFrontUrl(
'/public/emoji-datasource/'.concat(set, '/sheets-256/').concat(sheetSize, '.png'),
);
};
export default class EmojiPicker extends React.Component {
/*
props = {
preferedEmoji : Array of emoji's shortname
onChange : called when a smiley is selected
}
*/
constructor(props) {
super();
this.props = props;
this.state = {
suggestions: [],
currentTitle: '',
availableCategories: [],
scrollToRow: 0,
loaded: false,
};
this.currentTitleIndex = 100000;
this.clickScrollToRow = 0;
}
onChange(list) {
if (list.length > 0) {
if (this.props.onChange) {
this.props.onChange(list[0]);
}
this.setState({ list: [] });
}
}
renderItemChoosen(item) {
return (
<div className="itemResult itemResultChoosen">
<Emojione type={(item || {}).shortname} />
</div>
);
}
renderItem(item) {
return (
<div className="itemResult hoverable">
<Emojione type={item.shortname} />
</div>
);
}
scrollToCategory(category) {
var index = 0;
this.state.suggestions.forEach((item, i) => {
if (item.value == category) {
index = i;
return false;
}
});
this.clickScrollToRow = index;
if (this.state.scrollToRow != index) {
this.setState({ scrollToRow: index });
} else {
this.list_node.forceUpdateGrid();
}
}
select(emoji) {
if (this.picker) {
this.picker.onSelect(emoji);
} else {
this.onChange([emoji]);
}
}
getPrefered() {
var pref = this.props.preferedEmoji || [
':thumbsup:',
':thumbsdown:',
':hearts:',
':tada:',
':smile:',
':confused:',
];
if (
typeof this.props.selected === 'string' &&
this.props.selected &&
pref.indexOf(this.props.selected) < 0
) {
pref.unshift(this.props.selected);
}
if (isArray(this.props.selected)) {
this.props.selected.forEach(s => pref.unshift(s));
}
return pref;
}
componentDidMount() {
setTimeout(() => {
this.setState({ loaded: true });
}, 300);
}
render() {
if (!this.state.loaded) {
return <div style={{ height: 356 }}></div>;
}
return (
<div className="menu-cancel-margin">
<Picker
set="apple"
i18n={{
search: Languages.t('components.emoji_picker.input_search_placeholder'),
notfound: Languages.t('components.emoji_picker.categories.not_found'),
categories: {
search: Languages.t('components.emoji_picker.categories.search_result'),
recent: Languages.t('components.emoji_picker.categories.frequently_used'),
people: Languages.t('components.emoji_picker.categories.smileys_and_people'),
nature: Languages.t('components.emoji_picker.categories.animals_and_nature'),
foods: Languages.t('components.emoji_picker.categories.food_and_drink'),
activity: Languages.t('components.emoji_picker.categories.activity'),
places: Languages.t('components.emoji_picker.categories.travel_and_places'),
objects: Languages.t('components.emoji_picker.categories.objects'),
symbols: Languages.t('components.emoji_picker.categories.symbols'),
flags: Languages.t('components.emoji_picker.categories.flags'),
},
}}
showPreview={false}
showSkinTones={false}
autoFocus
perLine={6}
onSelect={this.props.onChange}
color={'var(--primary)'}
emojiSize={20}
/>
</div>
);
}
}
@@ -1,19 +0,0 @@
.emoji-mart {
border: none;
color: var(--grey-dark);
font-size: 14px;
width: unset !important;
left: 0px;
.emoji-mart-emoji {
padding: 7px;
}
}
.emoji-mart-search > input {
padding-top: 6px;
font-size: 14px;
}
.emoji-mart-emoji {
display: inline-flex;
align-items: center;
}
@@ -10,11 +10,9 @@ import {
isPendingFileStatusPending,
isPendingFileStatusSuccess,
} from '../../../features/files/utils/pending-files';
import Languages from 'app/features/global/services/languages-service';
import { useUpload } from 'app/features/files/hooks/use-upload';
import { PendingFileRecoilType, PendingFileType } from 'app/features/files/types/file';
import '../../file/file.scss';
import Languages from '@features/global/services/languages-service';
import { useUpload } from '@features/files/hooks/use-upload';
import { PendingFileRecoilType, PendingFileType } from '@features/files/types/file';
type PropsType = {
pendingFileState: PendingFileRecoilType;
@@ -6,9 +6,9 @@ import PerfectScrollbar from 'react-perfect-scrollbar';
import moment from 'moment';
import PendingFileRow from './pending-file-row';
import Languages from 'app/features/global/services/languages-service';
import { PendingFileRecoilType } from 'app/features/files/types/file';
import { useUpload } from 'app/features/files/hooks/use-upload';
import Languages from '@features/global/services/languages-service';
import { PendingFileRecoilType } from '@features/files/types/file';
import { useUpload } from '@features/files/hooks/use-upload';
import './styles.scss';
@@ -1,5 +1,5 @@
import React from 'react';
import { useUpload } from 'app/features/files/hooks/use-upload';
import { useUpload } from '@features/files/hooks/use-upload';
import PendingFilesList from './pending-file-components/pending-files-list';
const ChatUploadsViewer = (): JSX.Element => {
@@ -1,166 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useState, useEffect } from 'react';
import classNames, { Argument } from 'classnames';
import { FileThumbnail, FileDetails, FileActions, FileProgress } from './parts';
import {
isPendingFileStatusCancel,
isPendingFileStatusError,
isPendingFileStatusSuccess,
} from 'app/features/files/utils/pending-files';
import { DataFileType } from './types';
import DriveService from 'app/deprecated/Apps/Drive/Drive.js';
import RouterService from 'app/features/router/services/router-service';
import './file.scss';
import { PendingFileRecoilType } from 'app/features/files/types/file';
import Api from 'app/features/global/framework/api-service';
import FileUploadAPIClient from '../../features/files/api/file-upload-api-client';
import LargePreview from './parts/large-preview';
import { MessageFileType } from 'app/features/messages/types/message';
import { useFileViewerModal } from 'app/features/viewer/hooks/use-viewer';
type PropsType = {
source: 'internal' | 'drive' | string;
externalId: string | any;
file: DataFileType;
messageFile: MessageFileType;
context: 'input' | 'message' | 'drive';
progress?: number;
status?: PendingFileRecoilType['status'];
onRemove?: () => void;
className?: string;
large?: boolean;
xlarge?: boolean;
};
export default ({
source,
externalId,
file: _file,
messageFile,
className,
context,
progress,
status,
onRemove,
large,
xlarge,
}: PropsType) => {
const { companyId, workspaceId } = RouterService.getStateFromRoute();
const [file, setFile] = useState<DataFileType>(_file);
const classNameArguments: Argument[] = [
'file-component',
className,
{ 'large-view': large },
{
'file-component-error':
status && (isPendingFileStatusError(status) || isPendingFileStatusCancel(status)),
'file-component-uploading': progress != undefined && progress < 1,
},
];
const { open: openViewer } = useFileViewerModal();
useEffect(() => {
if (source === 'drive') {
(async () => {
if (typeof externalId === 'string') {
externalId = { id: externalId, workspace_id: workspaceId };
}
let driveFile = (await Api.post('/ajax/drive/v2/find', {
options: {
element_id: externalId?.id,
workspace_id: externalId?.workspace_id,
},
})) as any;
driveFile = driveFile?.data || {};
setFile({
...file,
thumbnail: driveFile.preview_link,
name: driveFile.name,
size: driveFile.size,
type: FileUploadAPIClient.mimeToType(
FileUploadAPIClient.extensionToMime(driveFile.extension),
),
});
})();
} else {
setFile(_file);
}
}, [_file]);
const onClickFile = async () => {
if (source === 'internal') {
//Only if upload has ended
if ((!status || isPendingFileStatusSuccess(status)) && file.id) openViewer(messageFile);
}
if (source === 'drive') {
if (typeof externalId === 'string') {
externalId = { id: externalId, workspace_id: workspaceId };
}
const file = (await Api.post('/ajax/drive/v2/find', {
options: {
element_id: externalId?.id,
workspace_id: externalId?.workspace_id,
},
})) as any;
DriveService.viewDocument(file?.data, context === 'input');
}
};
let computedHeight = 200;
let computedWidth = file.thumbnail_ratio * 200;
const isMediaFile = ['image', 'video'].includes(file.type);
if (xlarge) {
computedWidth = Math.max(
160,
Math.min(
Math.min(messageFile.metadata?.thumbnails?.[0]?.width || 600, 600),
file.thumbnail_ratio * document.body.clientHeight * 0.5,
),
);
computedHeight = Math.max(
200,
Math.min(
Math.min(
messageFile.metadata?.thumbnails?.[0]?.height || 10000,
document.body.clientHeight * 0.5,
),
computedWidth / file.thumbnail_ratio,
),
);
}
return (
<div
className={classNames(classNameArguments)}
style={large ? { width: computedWidth, height: computedHeight } : {}}
onClick={() => companyId && onClickFile()}
>
{large && <LargePreview file={file} />}
<div
className={classNames('file-info-container', {
'media-file-info-container': isMediaFile,
})}
>
<FileThumbnail file={file} />
<FileDetails file={file} source={source} />
<FileActions
deletable={context === 'input'}
actionMenu={context === 'message' && source === 'internal'}
status={status}
file={file}
messageFile={messageFile}
onRemove={onRemove}
source={source}
/>
</div>
<FileProgress progress={progress} status={status} file={file} />
</div>
);
};
@@ -1,160 +0,0 @@
.file-component {
border: 1px solid var(--grey-light);
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
border-radius: 8px 8px 8px 8px;
width: 218px;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
min-height: 50px;
&:hover {
cursor: pointer;
}
&.large-view {
min-height: 200px;
max-height: 500px;
max-width: 90%;
min-width: 160px;
position: relative;
.file-large-preview {
position: absolute;
height: 100%;
top: 0px;
left: 0px;
right: 0px;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
&:hover {
.file-info-container {
opacity: 1;
transition: opacity 0s;
}
}
.file-info-container {
background-color: #fffffff0;
position: absolute;
border-top: 1px solid var(--grey-light);
bottom: 0px;
width: 100%;
.file-thumbnail-container {
display: none;
}
}
.media-file-info-container {
opacity: 0;
transition: opacity 0.2s;
}
.file-large-preview-play-container {
width: 64px;
height: 64px;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
border-radius: 15px;
}
}
&.file-component-uploading {
border-radius: 8px 8px 0px 0px;
& .file-info-container {
opacity: 0.5;
}
.file-info-container {
padding-bottom: 0;
}
.file-progress-bar-container {
padding-top: 3px;
padding-bottom: 3px;
height: 5px;
}
}
&.file-component-error {
background: var(--error-background);
}
.file-info-container {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 8px 8px 6px 8px;
.file-thumbnail-container {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
.file-thumbnail-component {
border-radius: 4px;
}
}
.file-component-details {
overflow: hidden;
flex: 1;
display: flex;
flex-direction: column;
.ant-tag {
height: 15px;
line-height: 15px;
padding: 0 4px;
}
}
}
.file-menu {
display: flex;
align-items: center;
justify-content: space-between;
.ant-btn {
display: flex;
justify-content: center;
align-items: center;
&:hover {
color: var(--black);
}
color: var(--grey-dark);
background-color: transparent;
}
}
}
.file-progress-bar-container {
display: flex;
.file-progress-bar {
.ant-progress-outer {
height: 3px;
display: flex;
.ant-progress-inner {
border-radius: 0px;
.ant-progress-bg {
border-radius: 2px;
height: 3px !important;
transition: all 0.2s;
}
}
}
}
}
@@ -1,152 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useRef } from 'react';
import { Button } from 'antd';
import { MoreHorizontal, RotateCw, X } from 'react-feather';
import {
isPendingFileStatusPending,
isPendingFileStatusPause,
isPendingFileStatusSuccess,
isPendingFileStatusError,
isPendingFileStatusCancel,
} from 'app/features/files/utils/pending-files';
import Languages from 'app/features/global/services/languages-service';
import { useUpload } from 'app/features/files/hooks/use-upload';
import { DataFileType } from '../types';
import MenuManager from 'app/components/menus/menus-manager';
import { PendingFileRecoilType } from 'app/features/files/types/file';
import { MessageFileType } from 'app/features/messages/types/message';
import { useFileViewerModal } from 'app/features/viewer/hooks/use-viewer';
import { useEditors } from 'app/views/client/viewer/other/editors-service';
type PropsType = {
file: DataFileType;
messageFile: MessageFileType;
status?: PendingFileRecoilType['status'];
deletable?: boolean;
actionMenu?: boolean;
onRemove?: () => void;
source?: string;
};
export const FileActions = ({
file,
messageFile,
status,
deletable,
actionMenu,
onRemove,
}: PropsType): JSX.Element => {
const { cancelUpload, deleteOneFile, downloadOneFile, retryUpload } = useUpload();
const menuRef = useRef<HTMLElement>();
const { open: openPreview } = useFileViewerModal();
const { candidates } = useEditors(file.name.split('.').pop() || '');
const onClickDownload = async () => {
file.company_id &&
(await downloadOneFile({
companyId: file.company_id,
fileId: file.id,
messageFile,
}));
};
const onClickOpen = async () => {
openPreview(messageFile);
};
const buildMenu = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.stopPropagation();
const menu = [
{
type: 'menu',
text: Languages.t('scenes.apps.drive.download_button'),
onClick: onClickDownload,
},
];
if (candidates.length > 0) {
const openerName = candidates[0].name || candidates[0].app?.identity.name;
menu.push({
type: 'menu',
text: Languages.t(
'scenes.apps.drive.viewer.edit_with_button',
[openerName],
`Edit with ${openerName}`,
),
onClick: onClickOpen,
});
}
MenuManager.openMenu(menu, (window as any).getBoundingClientRect(menuRef.current), null, {
margin: 0,
});
};
const onClickCancel = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.preventDefault();
e.stopPropagation();
if (status && isPendingFileStatusSuccess(status)) {
if (file.id) deleteOneFile(file.id);
} else {
cancelUpload(file.id);
}
if (onRemove) onRemove();
};
const onClickRetry = () => {
retryUpload(file.id);
};
const setActions = () => {
if (actionMenu) {
return (
<Button
ref={node => node && (menuRef.current = node)}
shape="circle"
icon={<MoreHorizontal size={16} />}
onClick={buildMenu}
/>
);
}
if (deletable && status) {
if (isPendingFileStatusError(status)) {
return (
<>
<Button
shape="circle"
icon={<RotateCw size={16} color="var(--error)" />}
onClick={onClickRetry}
/>
<Button
shape="circle"
icon={<X size={16} color="var(--error)" />}
onClick={onClickCancel}
/>
</>
);
}
if (
isPendingFileStatusPending(status) ||
isPendingFileStatusPause(status) ||
isPendingFileStatusSuccess(status)
) {
return <Button shape="circle" icon={<X size={16} />} onClick={onClickCancel} />;
}
if (isPendingFileStatusCancel(status)) {
return <></>;
}
}
};
return <div className="file-menu">{setActions()}</div>;
};
export default FileActions;
@@ -1,37 +0,0 @@
import React from 'react';
import { Tag, Typography } from 'antd';
import { capitalize } from 'lodash';
import Numbers from 'app/features/global/utils/Numbers';
import { DataFileType } from '../types';
type PropsType = {
file: DataFileType;
source: string;
};
const setRealFileSize = (file: DataFileType): string => Numbers.humanFileSize(file.size, true);
const setFileType = (file: DataFileType): string => capitalize(file.type.split('/')[0]);
const { Text } = Typography;
export const FileDetails = ({ file, source }: PropsType) => {
let sourceTag: string | null = null;
if (source === 'drive') {
sourceTag = 'Drive';
}
return (
<div className="file-component-details">
<Text ellipsis style={{ verticalAlign: 'middle' }}>
{file.name}
</Text>
<Text type="secondary" ellipsis style={{ verticalAlign: 'middle' }}>
{!!sourceTag && <Tag color={'orange'}>{sourceTag}</Tag>}
{setRealFileSize(file)} {setFileType(file)}
</Text>
</div>
);
};
export default FileDetails;
@@ -1,4 +0,0 @@
export * from './details';
export * from './thumbnail';
export * from './actions';
export * from './progress';
@@ -1,33 +0,0 @@
import React from 'react';
import { DataFileType } from '../types';
import { Play } from 'react-feather';
type PropsType = {
file: DataFileType;
};
const LargePreview = ({ file: { thumbnail, type } }: PropsType): JSX.Element => {
return (
<div
className="file-large-preview"
style={{
backgroundImage: `url(${thumbnail})`,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
{type === 'video' && (
<div className='file-large-preview-play-container'>
<Play
size={32}
color={'white'}
strokeWidth={3}
/>
</div>
)}
</div>
);
};
export default LargePreview;
@@ -1,61 +0,0 @@
import React from 'react';
import { Progress } from 'antd';
import { DataFileType } from '../types';
import { PendingFileRecoilType } from 'app/features/files/types/file';
import {
isPendingFileStatusPending,
isPendingFileStatusPause,
isPendingFileStatusSuccess,
isPendingFileStatusError,
isPendingFileStatusCancel,
} from 'app/features/files/utils/pending-files';
type PropsType = {
file: DataFileType;
status?: PendingFileRecoilType['status'];
progress?: number;
};
const setStatus = (status: PendingFileRecoilType['status']): 'normal' | 'exception' | 'active' => {
switch (status) {
case 'error':
case 'pause':
return 'exception';
case 'pending':
return 'active';
default:
return 'normal';
}
};
export const FileProgress = ({ status, progress }: PropsType): JSX.Element => {
const setProgressStrokeColor = (): string => {
if (!status) return '';
if (isPendingFileStatusCancel(status)) return 'var(--error)';
if (isPendingFileStatusError(status)) return 'var(--error)';
if (isPendingFileStatusPause(status)) return 'var(--warning)';
if (isPendingFileStatusPending(status)) return 'var(--progress-bar-color)';
return 'var(--success)';
};
return status && !isPendingFileStatusSuccess(status) && progress != undefined ? (
<div className="file-progress-bar-container">
<Progress
type="line"
className="file-progress-bar"
percent={progress * 100}
showInfo={false}
status={setStatus(status)}
strokeColor={setProgressStrokeColor()}
trailColor="var(--progress-bar-background)"
/>
</div>
) : (
<div className="file-progress-bar-container" />
);
};
export default FileProgress;
@@ -1,42 +0,0 @@
import React from 'react';
import classNames from 'classnames';
import { FileText, Film, Headphones, Archive, Link, Image } from 'react-feather';
import { DataFileType } from '../types';
type PropsType = {
file: DataFileType;
};
export const FileThumbnail = ({ file }: PropsType): JSX.Element => {
const type = file.type;
const canHavePreview = ['image', 'video', 'pdf', 'document', 'slides', 'spreadsheet'].includes(
type,
);
return (
<div className={classNames('file-thumbnail-container', 'small-right-margin')}>
{canHavePreview && file.thumbnail && (
<div
className="ant-image file-thumbnail-component"
style={{
width: 32,
height: 32,
backgroundImage: `url(${file.thumbnail})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
)}
{type === 'image' && !file.thumbnail && <Image size={20} />}
{type === 'video' && !file.thumbnail && <Film size={20} />}
{['pdf', 'document', 'slides', 'spreadsheet', 'other'].includes(type) && !file.thumbnail && (
<FileText size={20} />
)}
{type === 'audio' && <Headphones size={20} />}
{type === 'archive' && <Archive size={20} />}
{type === 'link' && <Link size={20} />}
</div>
);
};
export default FileThumbnail;
@@ -1,9 +0,0 @@
export type DataFileType = {
id: string;
name: string;
thumbnail?: string;
thumbnail_ratio: number;
company_id?: string;
size: number;
type: string;
};
@@ -1,103 +0,0 @@
import { Button } from 'app/atoms/button/button';
import { Input } from 'app/atoms/input/input-text';
import { Modal, ModalContent } from 'app/atoms/modal';
import { ToasterService } from 'app/features/global/services/toaster-service';
import { useState } from 'react';
import { atom, useRecoilState } from 'recoil';
import { ChannelSelector } from '../channels-selector';
import MessageThreadAPIClient from 'features/messages/api/message-thread-api-client';
import { ChannelType } from 'app/features/channels/types/channel';
import { NodeMessage } from 'app/features/messages/types/message';
import Login from 'app/features/auth/login-service';
import { v1 as uuidv1 } from 'uuid';
import Languages from '../../features/global/services/languages-service';
export const ForwardMessageAtom = atom<null | {
id: string;
thread_id: string;
channel_id: string;
workspace_id: string;
company_id: string;
}>({
key: 'ForwardMessageAtom',
default: null,
});
export const ForwardMessageModal = () => {
const [message, setMessage] = useRecoilState(ForwardMessageAtom);
return (
<Modal
open={!!message}
onClose={() => setMessage(null)}
style={{ maxWidth: '600px', width: '100vw' }}
>
<ForwardMessage />
</Modal>
);
};
export const ForwardMessage = () => {
const [message, setMessage] = useRecoilState(ForwardMessageAtom);
const [channels, setChannels] = useState<ChannelType[]>([]);
const [loading, setLoading] = useState(false);
const [comment, setComment] = useState('');
return (
<ModalContent title={Languages.t('scenes.apps.messages.message.forward.title')}>
<ChannelSelector
initialChannels={[]}
onChange={channels => {
setChannels(channels);
}}
/>
<Input
className="w-full mt-2"
placeholder={Languages.t('scenes.apps.messages.message.forward.comment')}
value={comment}
onChange={e => setComment(e.target.value)}
/>
<Button
className="w-full mt-2 text-center justify-center"
disabled={channels.length === 0}
loading={loading}
onClick={async () => {
setLoading(true);
if (message) {
for (const channel of channels) {
await MessageThreadAPIClient.save(channel.company_id || '', {
message: {
thread_id: uuidv1(),
created_at: Date.now(),
user_id: Login.currentUserId,
context: {
_front_id: uuidv1(),
},
text: comment,
quote_message: {
...message,
} as unknown as NodeMessage,
} as NodeMessage,
participants: [
{
type: 'channel',
id: channel.id || '',
company_id: channel.company_id || '',
workspace_id: channel.workspace_id || '',
},
],
});
}
ToasterService.success(Languages.t('scenes.apps.messages.message.forward.send'));
setMessage(null);
}
}}
>
{Languages.t('scenes.apps.messages.message.forward.confirm', [channels.length])}
</Button>
</ModalContent>
);
};

Some files were not shown because too many files have changed in this diff Show More