Clean frontend files
@@ -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>
|
||||
)}
|
||||
|
||||
<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>
|
||||
);
|
||||
};
|
||||