Merge branch 'develop' into bugfix/selectors

This commit is contained in:
Timofey Boyko 2024-02-13 11:05:39 +03:00
commit ccce651266
101 changed files with 5092 additions and 1064 deletions

View File

@ -1,6 +1,6 @@
{
"name": "docspace",
"version": "2.0.2",
"version": "2.0.3",
"private": true,
"workspaces": {
"packages": [

View File

@ -1,6 +1,6 @@
{
"name": "@docspace/client",
"version": "2.0.2",
"version": "2.0.3",
"private": true,
"homepage": "",
"scripts": {

View File

@ -1,38 +1,100 @@
{
"ActionButton": "Action button",
"ActionButtonDescription": "You can disable the Action and + buttons in the current section header to limit creation of files, folders, and rooms.",
"AddWatermarks": "Add watermarks to documents",
"AdvancedDisplay": "Advanced display settings",
"APILink": "API library",
"Ascending": "Ascending",
"AllTypes": "All types",
"ButtonColor": "Button color",
"ButtonCustomization": "Button customization",
"ButtonText": "Button text",
"CancelButtonText": "Cancel button text",
"Code": "Code to insert",
"CopyWindowCode": "Copy window embed code",
"CreateSampleHeader": "Create sample DocSpace embed",
"Chat": "Chat",
"CSPDescription": "To safely embed DocSpace as an iframe in a website, add its URL to your allow list.",
"CSPHeader": "Embed DocSpace as iframe",
"CSPHelp": "This setting is a security mechanism that can be used to protect against content injection attacks. The CSP describes secure resource download sources. Downloading from resources not included in the `white list` is blocked. Specify the domains (together with the protocol) with which it will work.",
"CSPInputPlaceholder": "Enter URL like this: https://example.com",
"CustomizingDisplay": "Customizing the display",
"DefaultColumnsOption": "Default (Quantity depends on screen width)",
"DataDisplay": "Data display settings",
"Descending": "Descending",
"DisplayColumns": "Displaying columns in a file row",
"EmbedCodeSuccessfullyCopied": "Embed code successfully copied to clipboard",
"Editor": "Editor",
"EditorDescription": "Allows you to open the SDK as a document editor for editing by specifying the id parameter for a file.",
"EditorPresetDescription": "This mode allows you to open the SDK as a document editor for editing by specifying the id parameter for a file.",
"EnterCount": "Enter count",
"EnterHeight": "Enter height",
"EnterId": "Enter id",
"EnterPage": "Enter page number",
"EnterWidth": "Enter width",
"ElementItself": "The element itself",
"ElementCalledAfterClicking": "The element will be called after clicking",
"FeedbackAndSupport": "Feedback&Support",
"Filter": "Search, Filter and Sort",
"FileSelector": "File selector",
"FileSelectorDescription": "Opens the file selector and allows you to select a file from a list of available files.",
"FileSelectorPresetDescription": "Use this mode to display the file selector. It allows selecting a file from the list of the available ones.",
"FileTypeDisplay": "File type display",
"FrameId": "Frame id",
"FileId": "File ID",
"GetCode": "Get code to insert",
"Header": "Header",
"HeaderDescription": "You can disable header in the mobile version to limit access to the DocSpace sections (just like disabling the left menu in the desktop version).",
"InterfaceElements": "Interface elements",
"InitializeSDK": "Initialize the SDK in the following modes",
"InLeftPanel": "in the left panel",
"ItemsCount": "Items count on one page",
"ItemsCountDescription": "You can specify the number of files / folders displayed on one page, as well as specify which page to start displaying",
"JavascriptSdk": "Javascript SDK",
"Manager": "Manager",
"ManagerDescription": "Displays a list of entities depending on the specified rootPath. It allows you to create rooms, folders, and files and work with them.",
"ManagerPresetDescription": "Use this mode to display a list of entities depending on the specified rootPath. It allows creating and working with rooms, folders and files.",
"MainElementParameter": "Main element parameter",
"Menu": "Left menu",
"MenuDescription": "You can disable the left menu if users don't need to navigate to other sections.",
"MobileOnly": "only mobile devices",
"Page": "Display page (number)",
"RoomDescription": "You can select the room you want to display",
"RoomOrFolder": "Room or Folder",
"RoomOrFolderDescription": "You can select the section, room or folder you want to display",
"SDKDescription": "Using JavaScript SDK, you can embed a room or a folder from ONLYOFFICE DocSpace into your web interface as an iframe. Here, you can find settings for creating a sample iframe and configuring CSP. To use the complete SDK, please refer to the ",
"RoomSelector": "Room selector",
"RoomSelectorDescription": "Opens the room selector and allows you to select a room from a list of the available rooms.",
"RoomSelectorPresetDescription": "Use this mode to display the room selector. It allows selecting a room from the list of the available ones.",
"RoomTypeDisplay": "Room type display",
"Rotate": "Rotate",
"RightPanelCollapsed": "Right panel collapsed",
"Scale": "Scale",
"SDKDescription": "Using JavaScript SDK, you can embed one of the available ONLYOFFICE DocSpace modes into your web interface as an iframe (file manager, room or file selector, editor and viewer). Here, you can find settings for creating a sample iframe using modes and configuring CSP. To use the complete SDK, please refer to the ",
"SearchBlock": "Search block",
"ManagerSearchBlockDescription": "You can disable the search, filter and sort options.",
"FilesSearchDescription": "File search within the opened folder/room.",
"SearchTerm": "Search term",
"SelectToDocSpace": "Select to DocSpace",
"SelectImage": "Select image",
"SettingUpColumns": "Setting up Columns",
"SettingUpColumnsDescription": "You can disable the ability for users to manage and customize file information columns in list view.",
"SetItUp": "Set it up",
"SelectButtonText": "Select Button text",
"SelectFile": "Select a file",
"SelectRoom": "Select a room",
"SelectTypes": "Select types",
"SelectorPreview": "Selector preview",
"SortOrder": "Sort order",
"Title": "Navigate and Title"
"SetUp": "SET UP",
"SimpleRoom": "Simple Room",
"SimpleRoomDescription": "Opens the room selector and allows you to select a room from a list of the available rooms.",
"SimpleRoomPresetDescription": "Use this mode to display a list of entities depending on the specified rootPath. It allows creating and working with rooms, folders and files.",
"Subtitle": "Subtitle",
"SubtitleDescription": "Subtitle with additional comments or descriptions for the current directory.",
"TabPlugins": "Tab Plugins",
"Title": "Navigate and Title",
"ManagerTitleDescription": "You can disable the title of the current section/room/folder.",
"Viewer": "Viewer",
"ViewerDescription": "Allows you to open the SDK as a document editor for viewing by specifying the id parameter for a file.",
"ViewerPresetDescription": "Allows you to open the SDK as a document editor for viewing by specifying the id parameter for a file."
}

View File

@ -134,6 +134,9 @@ const ArticleMainButtonContent = (props) => {
isGracePeriod,
setInviteUsersWarningDialogVisible,
currentDeviceType,
isFrame,
disableActionButton,
} = props;
const navigate = useNavigate();
@ -492,7 +495,9 @@ const ArticleMainButtonContent = (props) => {
? t("Common:Invite")
: t("Common:Actions");
const isDisabled = isSettingsPage
const isDisabled = isFrame
? disableActionButton
: isSettingsPage
? isSettingsPage
: isAccountsPage
? !isAccountsPage
@ -638,6 +643,8 @@ export default inject(
const { setOformFromFolderId, oformsFilter } = oformsStore;
const { mainButtonItemsList } = pluginStore;
const { frameConfig, isFrame } = settingsStore
return {
isGracePeriod,
setInviteUsersWarningDialogVisible,
@ -680,6 +687,9 @@ export default inject(
versionHistoryPanelVisible,
security,
currentDeviceType,
isFrame,
disableActionButton: frameConfig?.disableActionButton,
};
}
)(

View File

@ -130,7 +130,7 @@ const Badges = ({
const iconForm =
sizeBadge === "medium" ? FormFillRectSvgUrl : AccessEditFormReactSvgUrl;
const iconEdit = !isForm ? FileActionsConvertEditDocReactSvgUrl : iconForm;
const iconEdit = !isPdf ? FileActionsConvertEditDocReactSvgUrl : iconForm;
const iconRefresh = desktopView ? Refresh12ReactSvgUrl : RefreshReactSvgUrl;
@ -206,7 +206,7 @@ const Badges = ({
/>
</BadgeWrapper>
)}
{isEditing && !isVisitor && !isPdf && !(isRecentTab && !canEditing) && (
{isEditing && !isVisitor && !(isRecentTab && !canEditing) && (
<ColorTheme
themeId={ThemeId.IconButton}
isEditing={isEditing}
@ -215,7 +215,7 @@ const Badges = ({
size={sizeBadge}
onClick={onFilesClick}
hoverColor={theme.filesBadges.hoverIconColor}
title={isForm ? t("Common:FillFormButton") : t("Common:EditButton")}
title={isPdf ? t("Common:FillFormButton") : t("Common:EditButton")}
/>
)}
{item.viewAccessibility?.MustConvert &&

View File

@ -20,6 +20,10 @@ export type FilesSelectorProps = {
onClose?: () => void;
id?: string | number;
withSearch: boolean;
withBreadCrumbs: boolean;
withSubtitle: boolean;
isMove?: boolean;
isCopy?: boolean;
isRestore: boolean;
@ -104,6 +108,8 @@ export type FilesSelectorProps = {
embedded: boolean;
withHeader: boolean;
withCancelButton: boolean;
cancelButtonLabel: string;
acceptButtonLabel: string;
settings: unknown;
roomsFolderId?: number;

View File

@ -41,6 +41,10 @@ const FilesSelectorWrapper = ({
onClose,
withSearch = true,
withBreadCrumbs = true,
withSubtitle = true,
isMove,
isCopy,
isRestore,
@ -96,6 +100,8 @@ const FilesSelectorWrapper = ({
embedded,
withHeader = true,
withCancelButton = true,
cancelButtonLabel,
acceptButtonLabel,
getIcon,
isRoomBackup,
@ -243,7 +249,7 @@ const FilesSelectorWrapper = ({
isRestore,
);
const acceptButtonLabel = getAcceptButtonLabel(
const defaultAcceptButtonLabel = getAcceptButtonLabel(
t,
isEditorDialog,
isCopy,
@ -309,7 +315,7 @@ const FilesSelectorWrapper = ({
getIsDisabled={getIsDisabledAction}
withHeader={withHeader}
headerLabel={headerLabel}
submitButtonLabel={acceptButtonLabel}
submitButtonLabel={acceptButtonLabel || defaultAcceptButtonLabel}
withCancelButton={withCancelButton}
isPanelVisible={isPanelVisible}
embedded={embedded}
@ -318,8 +324,12 @@ const FilesSelectorWrapper = ({
footerInputHeader={footerInputHeader || ""}
currentFooterInputValue={currentFooterInputValue || ""}
footerCheckboxLabel={footerCheckboxLabel || ""}
withoutBackButton
cancelButtonLabel={cancelButtonLabel}
withBreadCrumbs={withBreadCrumbs}
withSearch={withSearch}
descriptionText={
!filterParam || filterParam === "ALL"
!withSubtitle || !filterParam || filterParam === "ALL"
? ""
: descriptionText ?? t("Common:SelectDOCXFormat")
}
@ -362,6 +372,7 @@ export default inject(
isRestore,
isPanelVisible,
id,
currentFolderId,
}: FilesSelectorProps,
) => {
const { id: selectedId, parentId, rootFolderType } = selectedFolderStore;
@ -370,8 +381,6 @@ export default inject(
filesActionsStore;
const { itemOperationToFolder, clearActiveOperations } = uploadDataStore;
const sessionPath = window.sessionStorage.getItem("filesSelectorPath");
const { treeFolders, roomsFolderId } = treeFoldersStore;
const {
@ -419,6 +428,8 @@ export default inject(
: []
: [];
const sessionPath = window.sessionStorage.getItem("filesSelectorPath");
const selectionsWithoutEditing = isRestoreAll
? filesList
: isCopy
@ -445,13 +456,13 @@ export default inject(
? parentId
: selectedId);
const currentFolderId =
sessionPath && (isMove || isCopy || isRestore || isRestoreAll)
const folderId =
currentFolderId ||
(sessionPath && (isMove || isCopy || isRestore || isRestoreAll)
? +sessionPath
: fromFolderId;
: fromFolderId);
return {
currentFolderId,
fromFolderId,
parentId,
rootFolderType,
@ -487,6 +498,7 @@ export default inject(
getIcon,
roomsFolderId,
currentFolderId: folderId,
};
},
)(observer(FilesSelectorWrapper));

View File

@ -20,6 +20,21 @@ const StyledBadgesContainer = styled.div`
display: flex;
align-items: center;
${(props) =>
props.infoPanelVisible &&
css`
.accounts-badge:last-child {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 12px;
`
: css`
margin-right: 12px;
`}
}
`}
`;
const StyledPaidBadge = styled(Badge)`
@ -52,7 +67,7 @@ const Badges = ({
withoutPaid,
isPaid = false,
filter,
infoPanelVisible,
isSSO = false,
}) => {
const navigate = useNavigate();
@ -74,9 +89,13 @@ const Badges = ({
};
return (
<StyledBadgesContainer className="badges additional-badges">
<StyledBadgesContainer
className="badges additional-badges"
infoPanelVisible={infoPanelVisible}
>
{isSSO && (
<Badge
className="accounts-badge"
label={SSO_LABEL}
color={"#FFFFFF"}
backgroundColor="#22C386"
@ -89,7 +108,7 @@ const Badges = ({
)}
{!withoutPaid && isPaid && (
<StyledPaidBadge
className="paid-badge"
className="paid-badge accounts-badge"
label={t("Paid")}
backgroundColor={"#EDC409"}
fontSize={"9px"}
@ -101,10 +120,16 @@ const Badges = ({
/>
)}
{statusType === "pending" && (
<StyledSendClockIcon className="pending-badge" size="small" />
<StyledSendClockIcon
className="pending-badge accounts-badge"
size="small"
/>
)}
{statusType === "disabled" && (
<StyledCatalogSpamIcon className="disabled-badge" size="small" />
<StyledCatalogSpamIcon
className="disabled-badge accounts-badge"
size="small"
/>
)}
</StyledBadgesContainer>
);

View File

@ -123,6 +123,8 @@ const Table = ({
canChangeUserType,
isFiltered,
currentDeviceType,
typeAccountsColumnIsEnabled,
emailAccountsColumnIsEnabled,
}) => {
const ref = useRef(null);
const [hideColumns, setHideColumns] = React.useState(false);
@ -173,6 +175,9 @@ const Table = ({
canChangeUserType={canChangeUserType}
hideColumns={hideColumns}
itemIndex={index}
typeAccountsColumnIsEnabled={typeAccountsColumnIsEnabled}
emailAccountsColumnIsEnabled={emailAccountsColumnIsEnabled}
infoPanelVisible={infoPanelVisible}
/>
))}
</TableBody>
@ -189,6 +194,7 @@ export default inject(
accessRightsStore,
infoPanelStore,
userStore,
tableStore,
}) => {
const {
usersStore,
@ -205,6 +211,8 @@ export default inject(
const { isAdmin, isOwner, id: userId } = userStore.user;
const { canChangeUserType } = accessRightsStore;
const { typeAccountsColumnIsEnabled, emailAccountsColumnIsEnabled } =
tableStore;
return {
peopleList,
@ -224,6 +232,8 @@ export default inject(
canChangeUserType,
isFiltered,
currentDeviceType,
typeAccountsColumnIsEnabled,
emailAccountsColumnIsEnabled,
};
}
},
)(observer(Table));

View File

@ -28,7 +28,7 @@ class PeopleTableHeader extends React.Component {
{
key: "Type",
title: t("Common:Type"),
enable: true,
enable: props.typeAccountsColumnIsEnabled,
sortBy: "type",
resizable: true,
onChange: this.onColumnChange,
@ -44,7 +44,7 @@ class PeopleTableHeader extends React.Component {
{
key: "Mail",
title: t("Common:Email"),
enable: true,
enable: props.emailAccountsColumnIsEnabled,
resizable: true,
sortBy: "email",
onChange: this.onColumnChange,
@ -52,38 +52,19 @@ class PeopleTableHeader extends React.Component {
},
];
const columns = this.getColumns(defaultColumns);
const columns = props.getColumns(defaultColumns);
this.state = { columns };
}
getColumns = (defaultColumns) => {
const storageColumns = localStorage.getItem(
`${TABLE_COLUMNS}=${this.props.userId}`
);
const columns = [];
if (storageColumns) {
const splitColumns = storageColumns.split(",");
for (let col of defaultColumns) {
const column = splitColumns.find((key) => key === col.key);
column ? (col.enable = true) : (col.enable = false);
columns.push(col);
}
return columns;
} else {
return defaultColumns;
}
};
onColumnChange = (key, e) => {
const { columns } = this.state;
const columnIndex = columns.findIndex((c) => c.key === key);
if (columnIndex === -1) return;
this.props.setColumnEnable(key);
columns[columnIndex].enable = !columns[columnIndex].enable;
this.setState({ columns });
@ -179,6 +160,7 @@ export default inject(
infoPanelStore,
clientLoadingStore,
userStore,
tableStore
}) => {
const { filterStore } = peopleStore;
@ -186,14 +168,25 @@ export default inject(
const { isVisible: infoPanelVisible } = infoPanelStore;
const { withPaging } = settingsStore;
const {
getColumns,
setColumnEnable,
typeAccountsColumnIsEnabled,
emailAccountsColumnIsEnabled,
} = tableStore;
return {
filter,
setIsLoading: clientLoadingStore.setIsSectionBodyLoading,
userId: userStore.user?.id,
infoPanelVisible,
withPaging,
getColumns,
setColumnEnable,
typeAccountsColumnIsEnabled,
emailAccountsColumnIsEnabled,
};
}
)(

View File

@ -230,6 +230,9 @@ const PeopleTableRow = (props) => {
hideColumns,
value,
standalone,
typeAccountsColumnIsEnabled,
emailAccountsColumnIsEnabled,
infoPanelVisible,
} = props;
const {
@ -452,10 +455,19 @@ const PeopleTableRow = (props) => {
? displayName
: email}
</Link>
<Badges statusType={statusType} isPaid={isPaidUser} isSSO={isSSO} />
<Badges
statusType={statusType}
isPaid={isPaidUser}
isSSO={isSSO}
infoPanelVisible={infoPanelVisible}
/>
</TableCell>
<TableCell className={"table-cell_type"}>{typeCell}</TableCell>
{typeAccountsColumnIsEnabled ? (
<TableCell className={"table-cell_type"}>{typeCell}</TableCell>
) : (
<div />
)}
{/* <TableCell className="table-cell_room">
{!rooms?.length ? (
@ -498,20 +510,24 @@ const PeopleTableRow = (props) => {
)}
</TableCell> */}
<TableCell>
<Link
type="page"
title={email}
fontSize="13px"
fontWeight={600}
color={sideInfoColor}
onClick={onEmailClick}
isTextOverflow
enableUserSelect
>
{email}
</Link>
</TableCell>
{emailAccountsColumnIsEnabled ? (
<TableCell>
<Link
type="page"
title={email}
fontSize="13px"
fontWeight={600}
color={sideInfoColor}
onClick={onEmailClick}
isTextOverflow
enableUserSelect
>
{email}
</Link>
</TableCell>
) : (
<div />
)}
</StyledPeopleRow>
</StyledWrapper>
);

View File

@ -422,6 +422,7 @@ class FilesTableHeader extends React.Component {
tagRef,
setHideColumns,
isFrame,
showSettings,
} = this.props;
const {
@ -452,7 +453,7 @@ class FilesTableHeader extends React.Component {
tagRef={tagRef}
setHideColumns={setHideColumns}
settingsTitle={t("Files:TableSettingsTitle")}
showSettings={!isFrame}
showSettings={isFrame ? showSettings : true}
/>
);
}
@ -573,6 +574,7 @@ export default inject(
isFrame,
frameTableColumns: frameConfig?.viewTableColumns,
showSettings: frameConfig?.showSettings,
};
}
)(

View File

@ -247,6 +247,8 @@ const SectionHeaderContent = (props) => {
moveToPublicRoom,
currentDeviceType,
isFrame,
showTitle,
hideInfoPanel,
onClickArchive,
setLeaveRoomDialogVisible,
inRoom,
@ -1126,7 +1128,7 @@ const SectionHeaderContent = (props) => {
onPlusClick={onCreateRoom}
isEmptyPage={isEmptyPage}
isRoom={isCurrentRoom || isAccountsPage}
hideInfoPanel={isSettingsPage || isPublicRoom}
hideInfoPanel={hideInfoPanel || isSettingsPage || isPublicRoom}
withLogo={isPublicRoom && logo}
burgerLogo={isPublicRoom && burgerLogo}
isPublicRoom={isPublicRoom}
@ -1136,6 +1138,7 @@ const SectionHeaderContent = (props) => {
showRootFolderTitle={insideTheRoom}
currentDeviceType={currentDeviceType}
isFrame={isFrame}
showTitle={isFrame ? showTitle : true}
navigationButtonLabel={navigationButtonLabel}
onNavigationButtonClick={onNavigationButtonClick}
tariffBar={<TariffBar />}
@ -1271,6 +1274,7 @@ export default inject(
enablePlugins,
theme,
whiteLabelLogoUrls,
frameConfig,
isFrame,
currentDeviceType,
} = settingsStore;
@ -1324,9 +1328,10 @@ export default inject(
folderPath = navigationPath.filter((item) => !item.isRootRoom);
}
const isRoot = isFrame
? pathParts?.length === 1 || pathParts?.length === 2
: pathParts?.length === 1;
const isRoot =
isFrame && frameConfig?.id
? pathParts?.length === 1 || pathParts?.length === 2
: pathParts?.length === 1;
const haveLinksRight =
access === ShareAccessRights.RoomManager ||
@ -1456,6 +1461,8 @@ export default inject(
theme,
whiteLabelLogoUrls,
isFrame,
showTitle: frameConfig?.showTitle,
hideInfoPanel: isFrame && !frameConfig?.infoPanelVisible,
currentDeviceType,
setLeaveRoomDialogVisible,
inRoom,

View File

@ -293,11 +293,7 @@ const PureHome = (props) => {
<SectionWrapper {...sectionProps}>
{(!isErrorRoomNotAvailable || isAccountsPage || isSettingsPage) && (
<Section.SectionHeader>
{isFrame ? (
showTitle && <SectionHeaderContent />
) : (
<SectionHeaderContent />
)}
<SectionHeaderContent />
</Section.SectionHeader>
)}

View File

@ -320,6 +320,6 @@ export default inject(
}
)(
withLoading(
withTranslation(["Settings", "Common"])(observer(ArticleBodyContent))
withTranslation(["Settings", "Common", "JavascriptSdk"])(observer(ArticleBodyContent))
)
);

View File

@ -66,6 +66,14 @@ const CompanyInfoSettings = (props) => {
const navigate = useNavigate();
const location = useLocation();
const defaultCompanySettingsData = {
address: companyInfoSettingsData.address,
companyName: companyInfoSettingsData.companyName,
email: companyInfoSettingsData.email,
phone: companyInfoSettingsData.phone,
site: companyInfoSettingsData.site,
};
const defaultCompanySettingsError = {
hasErrorAddress: false,
hasErrorCompanyName: false,
@ -74,9 +82,11 @@ const CompanyInfoSettings = (props) => {
hasErrorSite: false,
};
const [companySettings, setCompanySettings] = useState({});
const [companySettings, setCompanySettings] = useState(
defaultCompanySettingsData,
);
const [companySettingsError, setCompanySettingsError] = useState(
defaultCompanySettingsError
defaultCompanySettingsError,
);
const [showReminder, setShowReminder] = useState(false);
const [isLoading, setIsLoading] = useState(false);
@ -114,18 +124,11 @@ const CompanyInfoSettings = (props) => {
setIsLoadedCompanyInfoSettingsData(true);
}, [companyInfoSettingsData, tReady]);
const getSettings = () => {
const getSettings = async () => {
await getCompanyInfoSettings();
const companySettings = getFromSessionStorage("companySettings");
const defaultData = {
address: companyInfoSettingsData?.address,
companyName: companyInfoSettingsData?.companyName,
email: companyInfoSettingsData?.email,
phone: companyInfoSettingsData?.phone,
site: companyInfoSettingsData?.site,
};
saveToSessionStorage("defaultCompanySettings", defaultData);
saveToSessionStorage("defaultCompanySettings", defaultCompanySettingsData);
if (companySettings) {
setCompanySettings({
@ -136,7 +139,7 @@ const CompanyInfoSettings = (props) => {
site: companySettings?.site,
});
} else {
setCompanySettings(defaultData);
setCompanySettings(defaultCompanySettingsData);
}
};
@ -146,7 +149,7 @@ const CompanyInfoSettings = (props) => {
useEffect(() => {
const defaultCompanySettings = getFromSessionStorage(
"defaultCompanySettings"
"defaultCompanySettings",
);
const newSettings = {
@ -182,7 +185,7 @@ const CompanyInfoSettings = (props) => {
const validateEmpty = (value, type) => {
const hasError = value.trim() === "";
const phoneRegex = /^[\d\(\)\-+]+$/;
const phoneRegex = /^[\d\(\)\-\s+]+$/;
const hasErrorPhone = !phoneRegex.test(value);
if (type === "companyName") {
@ -318,6 +321,13 @@ const CompanyInfoSettings = (props) => {
setShowModal(false);
};
const isDisabled =
hasErrorAddress ||
hasErrorCompanyName ||
hasErrorEmail ||
hasErrorPhone ||
hasErrorSite;
if (!isLoadedCompanyInfoSettingsData) return <LoaderCompanyInfoSettings />;
return (
@ -327,7 +337,7 @@ const CompanyInfoSettings = (props) => {
onClose={onCloseModal}
buildVersionInfo={buildVersionInfo}
personal={personal}
previewData={companySettings}
previewData={defaultCompanySettingsData}
/>
<StyledComponent isSettingPaid={isSettingPaid}>
@ -444,6 +454,7 @@ const CompanyInfoSettings = (props) => {
cancelButtonLabel={t("Common:Restore")}
reminderText={t("YouHaveUnsavedChanges")}
displaySettings={true}
saveButtonDisabled={isDisabled}
hasScroll={true}
hideBorder={true}
showReminder={(isSettingPaid && showReminder) || isLoading}
@ -488,6 +499,6 @@ export default inject(({ settingsStore, common, currentQuotaStore }) => {
};
})(
withLoading(
withTranslation(["Settings", "Common"])(observer(CompanyInfoSettings))
)
withTranslation(["Settings", "Common"])(observer(CompanyInfoSettings)),
),
);

View File

@ -16,7 +16,7 @@ import MobileView from "./Branding/MobileView";
import { UnavailableStyles } from "../../utils/commonSettingsStyles";
import { resetSessionStorage } from "../../utils";
import { useIsMobileView } from "../../utils/useIsMobileView";
import { DeviceType } from "@docspace/shared/enums";
const StyledComponent = styled.div`
max-width: 700px;
@ -60,8 +60,9 @@ const Branding = ({
isLoadedCompanyInfoSettingsData,
isSettingPaid,
standalone,
currentDeviceType
}) => {
const isMobileView = useIsMobileView();
const isMobileView = currentDeviceType === DeviceType.mobile;
useEffect(() => {
setDocumentTitle(t("Branding"));
@ -104,11 +105,12 @@ const Branding = ({
export default inject(({ settingsStore, currentQuotaStore, common }) => {
const { isBrandingAndCustomizationAvailable } = currentQuotaStore;
const { isLoadedCompanyInfoSettingsData } = common;
const { standalone } = settingsStore;
const { standalone, currentDeviceType } = settingsStore;
return {
isLoadedCompanyInfoSettingsData,
isSettingPaid: isBrandingAndCustomizationAvailable,
standalone,
currentDeviceType
};
})(withLoading(withTranslation(["Settings", "Common"])(observer(Branding))));

View File

@ -49,7 +49,7 @@ const PortalDeletion = (props) => {
try {
await sendDeletePortalEmail();
toastr.success(
t("PortalDeletionEmailSended", { ownerEmail: owner.email })
t("PortalDeletionEmailSended", { ownerEmail: owner.email }),
);
} catch (error) {
toastr.error(error);
@ -62,7 +62,7 @@ const PortalDeletion = (props) => {
};
const notActivatedEmail =
owner.activationStatus === EmployeeActivationStatus.NotActivated;
owner?.activationStatus === EmployeeActivationStatus.NotActivated;
return (
<MainContainer>
@ -115,5 +115,5 @@ export default inject(({ settingsStore, userStore }) => {
sendActivationLink,
};
})(
withTranslation(["Settings", "MainBar", "People", "Common"])(PortalDeletion)
withTranslation(["Settings", "MainBar", "People", "Common"])(PortalDeletion),
);

View File

@ -1,29 +1,32 @@
import { useState, useEffect } from "react";
import React from "react";
import { withTranslation } from "react-i18next";
import debounce from "lodash.debounce";
import styled, { css } from "styled-components";
import { Box } from "@docspace/shared/components/box";
import { TextInput } from "@docspace/shared/components/text-input";
import { Textarea } from "@docspace/shared/components/textarea";
import { Label } from "@docspace/shared/components/label";
import { Text } from "@docspace/shared/components/text";
import { Checkbox } from "@docspace/shared/components/checkbox";
import { ComboBox } from "@docspace/shared/components/combobox";
import { TabsContainer } from "@docspace/shared/components/tabs-container";
import FilesSelectorInput from "SRC_DIR/components/FilesSelectorInput";
import { mobile, tablet } from "@docspace/shared/utils";
import { objectToGetParams, loadScript } from "@docspace/shared/utils/common";
import { useNavigate } from "react-router-dom";
import { RoomsType } from "@docspace/shared/constants";
import { inject, observer } from "mobx-react";
import { mobile, tablet } from "@docspace/shared/utils/device";
import { isMobile } from "react-device-detect";
import { HelpButton } from "@docspace/shared/components/help-button";
import { Box } from "@docspace/shared/components/box";
import { Link } from "@docspace/shared/components/link";
import { Text } from "@docspace/shared/components/text";
import GetCodeDialog from "./sub-components/GetCodeDialog";
import CSP from "./sub-components/csp";
import { Button } from "@docspace/shared/components/button";
import PresetTile from "./sub-components/PresetTile";
const showPreviewThreshold = 720;
import SimpleRoomImg from "PUBLIC_DIR/images/sdk-presets_simple-room.react.svg?url";
import ManagerImg from "PUBLIC_DIR/images/sdk-presets_manager.react.svg?url";
import RoomSelectorImg from "PUBLIC_DIR/images/sdk-presets_room-selector.react.svg?url";
import FileSelectorImg from "PUBLIC_DIR/images/sdk-presets_file-selector.react.svg?url";
import EditorImg from "PUBLIC_DIR/images/sdk-presets_editor.react.svg?url";
import ViewerImg from "PUBLIC_DIR/images/sdk-presets_viewer.react.svg?url";
import SimpleRoomImgDark from "PUBLIC_DIR/images/sdk-presets_simple-room_dark.react.svg?url";
import ManagerImgDark from "PUBLIC_DIR/images/sdk-presets_manager_dark.react.svg?url";
import RoomSelectorImgDark from "PUBLIC_DIR/images/sdk-presets_room-selector_dark.react.svg?url";
import FileSelectorImgDark from "PUBLIC_DIR/images/sdk-presets_file-selector_dark.react.svg?url";
import EditorImgDark from "PUBLIC_DIR/images/sdk-presets_editor_dark.react.svg?url";
import ViewerImgDark from "PUBLIC_DIR/images/sdk-presets_viewer_dark.react.svg?url";
const SDKContainer = styled(Box)`
@media ${tablet} {
@ -36,35 +39,9 @@ const SDKContainer = styled(Box)`
`}
`;
const Controls = styled(Box)`
max-width: 350px;
min-width: 350px;
width: 100%;
display: flex;
flex-direction: column;
gap: 16px;
@media ${tablet} {
min-width: 0;
}
${isMobile &&
css`
min-width: 0;
`}
.label {
min-width: fit-content;
}
.checkbox {
max-width: fit-content;
}
`;
const CategoryHeader = styled.div`
margin-top: 40px;
margin-bottom: 24px;
margin-bottom: 16px;
font-size: ${(props) => props.theme.getCorrectFontSize("16px")};
font-style: normal;
font-weight: 700;
@ -80,448 +57,81 @@ const CategoryHeader = styled.div`
`}
`;
const CategorySubHeader = styled.div`
margin-top: 8px;
margin-bottom: 8px;
font-size: ${(props) => props.theme.getCorrectFontSize("15px")};
font-style: normal;
font-weight: 600;
line-height: 16px;
@media ${tablet} {
&:not(&.copy-window-code) {
margin-bottom: 0;
}
}
${isMobile &&
css`
&:not(&.copy-window-code) {
margin-bottom: 0;
}
`}
@media ${mobile} {
&:first-of-type {
margin-top: 0;
}
}
`;
const CategoryDescription = styled(Box)`
margin-top: 5px;
margin-top: 2px;
max-width: 700px;
.sdk-description {
display: inline;
line-height: 20px;
color: ${(props) => props.theme.client.settings.common.descriptionColor};
}
`;
const ControlsGroup = styled(Box)`
display: flex;
flex-direction: column;
gap: 8px;
@media ${tablet} {
gap: 4px;
}
${isMobile &&
css`
gap: 4px;
`}
`;
const LabelGroup = styled(Box)`
display: inline-flex;
align-items: center;
gap: 4px;
`;
const InterfaceElements = styled(Box)`
display: flex;
flex-direction: column;
const PresetsContainer = styled.div`
display: grid;
grid-template-columns: repeat(2, minmax(min(200px, 100%), 1fr));
gap: 16px;
margin-top: 24px;
`;
const Frame = styled(Box)`
max-width: fit-content;
margin-top: 16px;
position: relative;
border-radius: 6px;
border: 1px solid #d0d5da;
width: ${(props) => (props.width ? props.width : "100%")};
height: calc(${(props) => (props.height ? props.height : "400px")} + 2px);
@media ${tablet} {
margin-top: 4px;
}
${(props) =>
props.targetId &&
`
#${props.targetId} {
border-radius: 6px;
}
`}
${isMobile &&
css`
margin-top: 4px;
`}
`;
const Container = styled(Box)`
width: 100%;
display: flex;
flex-direction: row-reverse;
justify-content: flex-end;
gap: 16px;
@media ${tablet} {
flex-direction: column;
}
${isMobile &&
css`
flex-direction: column;
`}
`;
const RowContainer = styled(Box)`
flex-direction: row;
display: flex;
gap: 8px;
${(props) =>
props.combo &&
`
height: 32px;
align-items: center;
`}
`;
const ColumnContainer = styled(Box)`
flex-direction: column;
display: flex;
gap: 8px;
`;
const Preview = styled(Box)`
width: 100%;
margin-top: 24px;
min-width: 660px;
flex-direction: row;
@media ${tablet} {
margin-top: 0;
min-width: 0;
}
${isMobile &&
css`
margin-top: 0;
min-width: 0;
`}
`;
const GetCodeButtonWrapper = styled.div`
padding-block: 30px;
position: sticky;
bottom: 0;
margin-top: 32px;
background-color: ${({ theme }) => theme.backgroundColor};
@media ${mobile} {
position: fixed;
padding-inline: 16px;
inset-inline: 0;
}
`;
const FilesSelectorInputWrapper = styled.div`
& > div {
margin: 0;
display: flex;
flex-direction: column;
}
`;
const PortalIntegration = (props) => {
const {
t,
setDocumentTitle,
currentColorScheme,
sdkLink,
fetchExternalLinks,
} = props;
const { t, setDocumentTitle, currentColorScheme, sdkLink, theme } = props;
setDocumentTitle(t("JavascriptSdk"));
const scriptUrl = `${window.location.origin}/static/scripts/api.js`;
const navigate = useNavigate();
const dataSortBy = [
{ key: "DateAndTime", label: t("Common:LastModifiedDate"), default: true },
{ key: "AZ", label: t("Common:Title") },
{ key: "Type", label: t("Common:Type") },
{ key: "Size", label: t("Common:Size") },
{ key: "DateAndTimeCreation", label: t("Files:ByCreation") },
{ key: "Author", label: t("Files:ByAuthor") },
];
const navigateToSimpleRoom = () => navigate("room");
const navigateToManager = () => navigate("manager");
const navigateToRoomSelector = () => navigate("room-selector");
const navigateToFileSelector = () => navigate("file-selector");
const navigateToEditor = () => navigate("editor");
const navigateToViewer = () => navigate("viewer");
const dataSortOrder = [
{ key: "descending", label: t("Descending"), default: true },
{ key: "ascending", label: t("Ascending") },
];
const dataDimensions = [
{ key: "percent", label: "%", default: true },
{ key: "pixel", label: "px" },
];
const [sortBy, setSortBy] = useState(dataSortBy[0]);
const [sortOrder, setSortOrder] = useState(dataSortOrder[0]);
const [widthDimension, setWidthDimension] = useState(dataDimensions[0]);
const [heightDimension, setHeightDimension] = useState(dataDimensions[1]);
const [width, setWidth] = useState("100");
const [height, setHeight] = useState("600");
const [withSubfolders, setWithSubfolders] = useState(false);
const [isGetCodeDialogOpened, setIsGetCodeDialogOpened] = useState(false);
const [showPreview, setShowPreview] = useState(
window.innerWidth > showPreviewThreshold
);
const [sharedLinks, setSharedLinks] = useState(null);
const [config, setConfig] = useState({
width: `${width}${widthDimension.label}`,
height: `${height}${heightDimension.label}`,
frameId: "ds-frame",
showHeader: true,
showTitle: true,
showMenu: true,
showFilter: true,
init: true,
});
const params = objectToGetParams(config);
const frameId = config.frameId || "ds-frame";
const destroyFrame = () => {
window.DocSpace?.SDK?.frames[frameId]?.destroyFrame();
};
const loadFrame = debounce(() => {
const script = document.getElementById("integration");
if (script) {
script.remove();
}
const params = objectToGetParams(config);
loadScript(`${scriptUrl}${params}`, "integration", () =>
window.DocSpace.SDK.initFrame(config)
);
}, 500);
useEffect(() => {
loadFrame();
return () => destroyFrame();
});
const onChangeTab = () => {
loadFrame();
};
const onChangeWidth = (e) => {
setConfig((config) => {
return { ...config, width: `${e.target.value}${widthDimension.label}` };
});
setWidth(e.target.value);
};
const onChangeHeight = (e) => {
setConfig((config) => {
return { ...config, height: `${e.target.value}${heightDimension.label}` };
});
setHeight(e.target.value);
};
const onChangeFolderId = async (id, publicInPath) => {
let newConfig = { id, requestToken: null, rootPath: "/rooms/shared/" };
if (!!publicInPath) {
const links = await fetchExternalLinks(publicInPath.id);
if (links.length > 1) {
const linksOptions = links.map((link) => {
const { id, title, requestToken } = link.sharedTo;
return {
key: id,
label: title,
requestToken: requestToken,
};
});
setSharedLinks(linksOptions);
}
newConfig.requestToken = links[0].sharedTo?.requestToken;
newConfig.rootPath = "/rooms/share";
} else {
setSharedLinks(null);
}
setConfig((config) => {
return { ...config, ...newConfig };
});
};
const onChangeSharedLink = (link) => {
setConfig((config) => {
return { ...config, requestToken: link.requestToken };
});
};
const onChangeFrameId = (e) => {
setConfig((config) => {
return { ...config, frameId: e.target.value };
});
};
const onChangeWithSubfolders = (e) => {
setConfig((config) => {
return { ...config, withSubfolders: !withSubfolders };
});
setWithSubfolders(!withSubfolders);
};
const onChangeSortBy = (item) => {
setConfig((config) => {
return { ...config, sortby: item.key };
});
setSortBy(item);
};
const onChangeSortOrder = (item) => {
setConfig((config) => {
return { ...config, sortorder: item.key };
});
setSortOrder(item);
};
const onChangeWidthDimension = (item) => {
setConfig((config) => {
return { ...config, width: `${width}${item.label}` };
});
setWidthDimension(item);
};
const onChangeHeightDimension = (item) => {
setConfig((config) => {
return { ...config, height: `${height}${item.label}` };
});
setHeightDimension(item);
};
const onChangeShowHeader = (e) => {
setConfig((config) => {
return { ...config, showHeader: !config.showHeader };
});
};
const onChangeShowTitle = () => {
setConfig((config) => {
return { ...config, showTitle: !config.showTitle };
});
};
const onChangeShowMenu = (e) => {
setConfig((config) => {
return { ...config, showMenu: !config.showMenu };
});
};
const onChangeShowFilter = (e) => {
setConfig((config) => {
return { ...config, showFilter: !config.showFilter };
});
};
const onChangeCount = (e) => {
setConfig((config) => {
return { ...config, count: e.target.value };
});
};
const onChangePage = (e) => {
setConfig((config) => {
return { ...config, page: e.target.value };
});
};
const onChangeSearch = (e) => {
setConfig((config) => {
return { ...config, search: e.target.value };
});
};
const openGetCodeModal = () => setIsGetCodeDialogOpened(true);
const closeGetCodeModal = () => setIsGetCodeDialogOpened(false);
const onResize = () => {
const isEnoughWidthForPreview = window.innerWidth > showPreviewThreshold;
if (isEnoughWidthForPreview !== showPreview)
setShowPreview(isEnoughWidthForPreview);
};
useEffect(() => {
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
}, [showPreview]);
const codeBlock = `<div id="${frameId}">Fallback text</div>\n<script src="${scriptUrl}${params}"></script>`;
const preview = (
<Frame
width={width + widthDimension.label}
height={height + heightDimension.label}
targetId={frameId}
>
<Box id={frameId}></Box>
</Frame>
);
const code = (
<>
<CategorySubHeader className="copy-window-code">
{t("CopyWindowCode")}
</CategorySubHeader>
<Textarea value={codeBlock} heightTextArea="153px" />
</>
);
const dataTabs = [
const presetsData = [
{
key: "preview",
title: t("Common:Preview"),
content: preview,
title: t("Common:Room"),
description: t("SimpleRoomDescription"),
image: theme.isBase ? SimpleRoomImg : SimpleRoomImgDark,
handleOnClick: navigateToSimpleRoom,
},
{
key: "code",
title: t("Code"),
content: code,
title: t("Manager"),
description: t("ManagerDescription"),
image: theme.isBase ? ManagerImg : ManagerImgDark,
handleOnClick: navigateToManager,
},
{
title: t("Editor"),
description: t("EditorDescription"),
image: theme.isBase ? EditorImg : EditorImgDark,
handleOnClick: navigateToEditor,
},
{
title: t("Viewer"),
description: t("ViewerDescription"),
image: theme.isBase ? ViewerImg : ViewerImgDark,
handleOnClick: navigateToViewer,
},
{
title: t("RoomSelector"),
description: t("RoomSelectorDescription"),
image: theme.isBase ? RoomSelectorImg : RoomSelectorImgDark,
handleOnClick: navigateToRoomSelector,
},
{
title: t("FileSelector"),
description: t("FileSelectorDescription"),
image: theme.isBase ? FileSelectorImg : FileSelectorImgDark,
handleOnClick: navigateToFileSelector,
},
];
@ -531,7 +141,7 @@ const PortalIntegration = (props) => {
<Text className="sdk-description">{t("SDKDescription")}</Text>
<Link
color={currentColorScheme?.main?.accent}
fontSize="12px"
fontSize="13px"
fontWeight="400"
onClick={() => window.open(sdkLink, "_blank")}
>
@ -540,233 +150,21 @@ const PortalIntegration = (props) => {
<CSP t={t} />
</CategoryDescription>
<CategoryHeader>{t("CreateSampleHeader")}</CategoryHeader>
<Container>
{showPreview && (
<Preview>
<TabsContainer onSelect={onChangeTab} elements={dataTabs} />
</Preview>
)}
<Controls>
<CategorySubHeader>{t("CustomizingDisplay")}</CategorySubHeader>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Width")} />
<RowContainer combo>
<TextInput
onChange={onChangeWidth}
placeholder={t("EnterWidth")}
value={width}
tabIndex={2}
/>
<ComboBox
size="content"
scaled={false}
scaledOptions={true}
onSelect={onChangeWidthDimension}
options={dataDimensions}
selectedOption={widthDimension}
displaySelectedOption
directionY="bottom"
/>
</RowContainer>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Height")} />
<RowContainer combo>
<TextInput
onChange={onChangeHeight}
placeholder={t("EnterHeight")}
value={height}
tabIndex={3}
/>
<ComboBox
size="content"
scaled={false}
scaledOptions={true}
onSelect={onChangeHeightDimension}
options={dataDimensions}
selectedOption={heightDimension}
displaySelectedOption
directionY="bottom"
/>
</RowContainer>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("FrameId")} />
<TextInput
scale={true}
onChange={onChangeFrameId}
placeholder={t("EnterId")}
value={config.frameId}
tabIndex={4}
/>
</ControlsGroup>
<InterfaceElements>
<Label className="label">{t("InterfaceElements")}</Label>
<Checkbox
className="checkbox"
label={t("Menu")}
onChange={onChangeShowMenu}
isChecked={config.showMenu}
/>
<Checkbox
className="checkbox"
label={t("Header")}
onChange={onChangeShowHeader}
isChecked={config.showHeader}
/>
<Checkbox
className="checkbox"
label={t("Filter")}
onChange={onChangeShowFilter}
isChecked={config.showFilter}
/>
<RowContainer>
<Checkbox
label={t("Title")}
onChange={onChangeShowTitle}
isChecked={config.showTitle}
/>
<Text color="gray">{`(${t("MobileOnly")})`}</Text>
</RowContainer>
</InterfaceElements>
<CategorySubHeader>{t("DataDisplay")}</CategorySubHeader>
<ControlsGroup>
<LabelGroup>
<Label className="label" text={t("RoomOrFolder")} />
<HelpButton
offsetRight={0}
size={12}
tooltipContent={
<Text fontSize="12px">{t("RoomOrFolderDescription")}</Text>
}
/>
</LabelGroup>
<FilesSelectorInputWrapper>
<FilesSelectorInput onSelectFolder={onChangeFolderId} isSelect />
</FilesSelectorInputWrapper>
</ControlsGroup>
{sharedLinks && (
<ControlsGroup>
<LabelGroup>
<Label
className="label"
text={t("SharingPanel:ExternalLink")}
/>
<HelpButton
offsetRight={0}
size={12}
tooltipContent={
<Text fontSize="12px">
{t("CreateEditRoomDialog:PublicRoomDescription")}
</Text>
}
/>
</LabelGroup>
<ComboBox
scaled={true}
onSelect={onChangeSharedLink}
options={sharedLinks}
selectedOption={sharedLinks[0]}
displaySelectedOption
directionY="bottom"
/>
</ControlsGroup>
)}
<CategorySubHeader>{t("AdvancedDisplay")}</CategorySubHeader>
<ControlsGroup>
<Label className="label" text={t("SearchTerm")} />
<ColumnContainer>
<TextInput
scale={true}
onChange={onChangeSearch}
placeholder={t("Common:Search")}
value={config.search}
tabIndex={5}
/>
<Checkbox
className="checkbox"
label={t("Files:WithSubfolders")}
onChange={onChangeWithSubfolders}
isChecked={withSubfolders}
/>
</ColumnContainer>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("Common:SortBy")} />
<ComboBox
onSelect={onChangeSortBy}
options={dataSortBy}
scaled={true}
selectedOption={sortBy}
displaySelectedOption
directionY="top"
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("SortOrder")} />
<ComboBox
onSelect={onChangeSortOrder}
options={dataSortOrder}
scaled={true}
selectedOption={sortOrder}
displaySelectedOption
directionY="top"
/>
</ControlsGroup>
<ControlsGroup>
<LabelGroup>
<Label className="label" text={t("ItemsCount")} />
<HelpButton
offsetRight={0}
size={12}
tooltipContent={
<Text fontSize="12px">{t("ItemsCountDescription")}</Text>
}
/>
</LabelGroup>
<TextInput
scale={true}
onChange={onChangeCount}
placeholder={t("EnterCount")}
value={config.count}
tabIndex={6}
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("Page")} />
<TextInput
scale={true}
onChange={onChangePage}
placeholder={t("EnterPage")}
value={config.page}
isDisabled={!config.count}
tabIndex={7}
/>
</ControlsGroup>
</Controls>
</Container>
{!showPreview && (
<>
<GetCodeButtonWrapper>
<Button
id="get-sdk-code-button"
primary
size="normal"
scale
label={t("GetCode")}
onClick={openGetCodeModal}
/>
</GetCodeButtonWrapper>
<GetCodeDialog
<Text lineHeight="20px" color={theme.sdkPresets.secondaryColor}>
{t("InitializeSDK")}
</Text>
<PresetsContainer>
{presetsData.map((data) => (
<PresetTile
t={t}
visible={isGetCodeDialogOpened}
codeBlock={codeBlock}
onClose={closeGetCodeModal}
key={data.title}
title={data.title}
description={data.description}
image={data.image}
handleOnClick={data.handleOnClick}
/>
</>
)}
))}
</PresetsContainer>
</SDKContainer>
);
};
@ -774,14 +172,12 @@ const PortalIntegration = (props) => {
export default inject(({ settingsStore, authStore, publicRoomStore }) => {
const { setDocumentTitle } = authStore;
const { theme, currentColorScheme, sdkLink } = settingsStore;
const { fetchExternalLinks } = publicRoomStore;
return {
theme,
setDocumentTitle,
currentColorScheme,
sdkLink,
fetchExternalLinks,
};
})(
withTranslation([
@ -791,5 +187,5 @@ export default inject(({ settingsStore, authStore, publicRoomStore }) => {
"CreateEditRoomDialog",
"SharingPanel",
"Common",
])(observer(PortalIntegration))
])(observer(PortalIntegration)),
);

View File

@ -0,0 +1,350 @@
import { useState, useEffect } from "react";
import { withTranslation } from "react-i18next";
import debounce from "lodash.debounce";
import { Box } from "@docspace/shared/components/box";
import { TextInput } from "@docspace/shared/components/text-input";
import { Textarea } from "@docspace/shared/components/textarea";
import { Label } from "@docspace/shared/components/label";
import { Text } from "@docspace/shared/components/text";
import { Checkbox } from "@docspace/shared/components/checkbox";
import { ComboBox } from "@docspace/shared/components/combobox";
import { TabsContainer } from "@docspace/shared/components/tabs-container";
import FilesSelectorInput from "SRC_DIR/components/FilesSelectorInput";
import { objectToGetParams, loadScript } from "@docspace/shared/utils/common";
import { inject, observer } from "mobx-react";
import { FilesSelectorFilterTypes } from "@docspace/shared/enums";
import { isTablet, isMobile } from "@docspace/shared/utils/device";
import EmptyIframeContainer from "../sub-components/EmptyIframeContainer";
import GetCodeDialog from "../sub-components/GetCodeDialog";
import { Button } from "@docspace/shared/components/button";
const showPreviewThreshold = 720;
import {
SDKContainer,
Controls,
CategoryHeader,
CategorySubHeader,
CategoryDescription,
ControlsGroup,
LabelGroup,
ControlsSection,
Frame,
Container,
RowContainer,
Preview,
GetCodeButtonWrapper,
FilesSelectorInputWrapper,
CodeWrapper,
} from "./StyledPresets";
const Editor = (props) => {
const { t, setDocumentTitle } = props;
setDocumentTitle(t("JavascriptSdk"));
const scriptUrl = `${window.location.origin}/static/scripts/api.js`;
const dataDimensions = [
{ key: "percent", label: "%", default: true },
{ key: "pixel", label: "px" },
];
const [widthDimension, setWidthDimension] = useState(dataDimensions[0]);
const [heightDimension, setHeightDimension] = useState(dataDimensions[0]);
const [width, setWidth] = useState("100");
const [height, setHeight] = useState("100");
const [isGetCodeDialogOpened, setIsGetCodeDialogOpened] = useState(false);
const [showPreview, setShowPreview] = useState(window.innerWidth > showPreviewThreshold);
const [config, setConfig] = useState({
mode: "editor",
width: `${width}${widthDimension.label}`,
height: `${height}${heightDimension.label}`,
frameId: "ds-frame",
init: false,
});
const params = objectToGetParams(config);
const frameId = config.frameId || "ds-frame";
const destroyFrame = () => {
window.DocSpace?.SDK?.frames[frameId]?.destroyFrame();
};
const loadFrame = debounce(() => {
const script = document.getElementById("integration");
if (script) {
script.remove();
}
const params = objectToGetParams(config);
loadScript(`${scriptUrl}${params}`, "integration", () => window.DocSpace.SDK.initFrame(config));
}, 500);
useEffect(() => {
loadFrame();
return () => destroyFrame();
});
const onChangeTab = () => {
loadFrame();
};
const onChangeWidth = (e) => {
setConfig((config) => {
return { ...config, width: `${e.target.value}${widthDimension.label}` };
});
setWidth(e.target.value);
};
const onChangeHeight = (e) => {
setConfig((config) => {
return { ...config, height: `${e.target.value}${heightDimension.label}` };
});
setHeight(e.target.value);
};
const onChangeFileId = (file) => {
setConfig((config) => {
return { ...config, id: file.id, init: true };
});
};
const onChangeFrameId = (e) => {
setConfig((config) => {
return { ...config, frameId: e.target.value };
});
};
const onChangeWidthDimension = (item) => {
setConfig((config) => {
return { ...config, width: `${width}${item.label}` };
});
setWidthDimension(item);
};
const onChangeHeightDimension = (item) => {
setConfig((config) => {
return { ...config, height: `${height}${item.label}` };
});
setHeightDimension(item);
};
const openGetCodeModal = () => setIsGetCodeDialogOpened(true);
const closeGetCodeModal = () => setIsGetCodeDialogOpened(false);
const onResize = () => {
const isEnoughWidthForPreview = window.innerWidth > showPreviewThreshold;
if (isEnoughWidthForPreview !== showPreview) setShowPreview(isEnoughWidthForPreview);
};
useEffect(() => {
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
}, [showPreview]);
const codeBlock = `<div id="${frameId}">Fallback text</div>\n<script src="${scriptUrl}${params}"></script>`;
const preview = (
<Frame
width={width + widthDimension.label}
height={height + heightDimension.label}
targetId={frameId}
>
{config.id !== undefined ? (
<>
<Box id={frameId}></Box>
</>
) : (
<EmptyIframeContainer
text={t("SelectFile")}
width={width + widthDimension.label}
height={height + heightDimension.label}
/>
)}
</Frame>
);
const code = (
<CodeWrapper>
<CategorySubHeader className="copy-window-code">{t("CopyWindowCode")}</CategorySubHeader>
<Textarea value={codeBlock} heightTextArea={153} />
</CodeWrapper>
);
const dataTabs = [
{
key: "preview",
title: t("Common:Preview"),
content: preview,
},
{
key: "code",
title: t("Code"),
content: code,
},
];
return (
<SDKContainer>
<CategoryDescription>
<Text className="sdk-description">{t("EditorPresetDescription")}</Text>
</CategoryDescription>
<CategoryHeader>{t("CreateSampleHeader")}</CategoryHeader>
<Container>
{showPreview && (
<Preview>
<TabsContainer
isDisabled={config?.id === undefined}
onSelect={onChangeTab}
elements={dataTabs}
/>
</Preview>
)}
<Controls>
<ControlsSection>
<CategorySubHeader>{t("FileId")}</CategorySubHeader>
<ControlsGroup>
<LabelGroup>
<Label className="label" text={t("Common:SelectFile")} />
</LabelGroup>
<FilesSelectorInputWrapper>
<FilesSelectorInput
onSelectFile={onChangeFileId}
filterParam={FilesSelectorFilterTypes.ALL}
isSelect
/>
</FilesSelectorInputWrapper>
</ControlsGroup>
</ControlsSection>
<ControlsSection>
<CategorySubHeader>{t("CustomizingDisplay")}</CategorySubHeader>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Width")} />
<RowContainer combo>
<TextInput
onChange={onChangeWidth}
placeholder={t("EnterWidth")}
value={width}
tabIndex={2}
/>
<ComboBox
size="content"
scaled={false}
scaledOptions={true}
onSelect={onChangeWidthDimension}
options={dataDimensions}
selectedOption={widthDimension}
displaySelectedOption
directionY="bottom"
/>
</RowContainer>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Height")} />
<RowContainer combo>
<TextInput
onChange={onChangeHeight}
placeholder={t("EnterHeight")}
value={height}
tabIndex={3}
/>
<ComboBox
size="content"
scaled={false}
scaledOptions={true}
onSelect={onChangeHeightDimension}
options={dataDimensions}
selectedOption={heightDimension}
displaySelectedOption
directionY="bottom"
/>
</RowContainer>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("FrameId")} />
<TextInput
scale={true}
onChange={onChangeFrameId}
placeholder={t("EnterId")}
value={config.frameId}
tabIndex={4}
/>
</ControlsGroup>
</ControlsSection>
{/* <InterfaceElements>
<Label className="label">{t("InterfaceElements")}</Label>
<Checkbox
className="checkbox"
label={t("RightPanelCollapsed")}
onChange={() => {}}
isChecked={true}
/>
<Checkbox
className="checkbox"
label={t("TabPlugins")}
onChange={() => {}}
isChecked={true}
/>
<RowContainer>
<Checkbox label={t("Chat")} onChange={() => {}} isChecked={true} />
<Text color="gray">({t("InLeftPanel")})</Text>
</RowContainer>
<RowContainer>
<Checkbox label={t("FeedbackAndSupport")} onChange={() => {}} isChecked={true} />
<Text color="gray">({t("InLeftPanel")})</Text>
</RowContainer>
</InterfaceElements> */}
</Controls>
</Container>
{!showPreview && (
<>
<GetCodeButtonWrapper>
<Button
id="get-sdk-code-button"
primary
size="normal"
scale
label={t("GetCode")}
onClick={openGetCodeModal}
/>
</GetCodeButtonWrapper>
<GetCodeDialog
t={t}
visible={isGetCodeDialogOpened}
codeBlock={codeBlock}
onClose={closeGetCodeModal}
/>
</>
)}
</SDKContainer>
);
};
export default inject(({ authStore, settingsStore }) => {
const { setDocumentTitle } = authStore;
const { theme } = settingsStore;
return {
theme,
setDocumentTitle,
};
})(withTranslation(["JavascriptSdk", "Files", "EmbeddingPanel", "Common"])(observer(Editor)));

View File

@ -0,0 +1,693 @@
import { useState, useEffect } from "react";
import { withTranslation } from "react-i18next";
import debounce from "lodash.debounce";
import { Box } from "@docspace/shared/components/box";
import { TextInput } from "@docspace/shared/components/text-input";
import { Textarea } from "@docspace/shared/components/textarea";
import { Label } from "@docspace/shared/components/label";
import { Text } from "@docspace/shared/components/text";
import { Checkbox } from "@docspace/shared/components/checkbox";
import { ComboBox } from "@docspace/shared/components/combobox";
import { TabsContainer } from "@docspace/shared/components/tabs-container";
import FilesSelectorInput from "SRC_DIR/components/FilesSelectorInput";
import { RadioButtonGroup } from "@docspace/shared/components/radio-button-group";
import { SelectedItem } from "@docspace/shared/components/selected-item";
import { ColorInput } from "@docspace/shared/components/color-input";
import { objectToGetParams, loadScript } from "@docspace/shared/utils/common";
import { inject, observer } from "mobx-react";
import { isTablet, isMobile } from "@docspace/shared/utils/device";
import { HelpButton } from "@docspace/shared/components/help-button";
import { Button } from "@docspace/shared/components/button";
import GetCodeDialog from "../sub-components/GetCodeDialog";
import { FilesSelectorFilterTypes } from "@docspace/shared/enums";
import { TooltipContent } from "../sub-components/TooltipContent";
import SubtitleUrl from "PUBLIC_DIR/images/sdk-presets_subtitle.react.svg?url";
import SearchUrl from "PUBLIC_DIR/images/sdk-presets_files-search.react.svg?url";
import { toastr } from "@docspace/shared/components/toast";
const showPreviewThreshold = 720;
import {
SDKContainer,
Controls,
CategoryHeader,
CategorySubHeader,
CategoryDescription,
ControlsGroup,
LabelGroup,
ControlsSection,
Frame,
Container,
RowContainer,
Preview,
GetCodeButtonWrapper,
FilesSelectorInputWrapper,
SelectedItemsContainer,
CodeWrapper,
} from "./StyledPresets";
const FileSelector = (props) => {
const { t, setDocumentTitle, fetchExternalLinks } = props;
setDocumentTitle(t("JavascriptSdk"));
const scriptUrl = `${window.location.origin}/static/scripts/api.js`;
const dataDimensions = [
{ key: "percent", label: "%", default: true },
{ key: "pixel", label: "px" },
];
const elementDisplayOptions = [
{ value: "element", label: t("ElementItself") },
{
value: "button",
label: (
<RowContainer>
{t("Common:Button")}
<Text color="gray">{`(${t("ElementCalledAfterClicking")})`}</Text>
</RowContainer>
),
},
];
const fileTypeDisplay = [
{ value: FilesSelectorFilterTypes.ALL, label: t("AllTypes") },
{ value: "custom-types", label: t("SelectTypes") },
];
const [fileOptions, setFileOptions] = useState([
{ key: FilesSelectorFilterTypes.DOCX, label: FilesSelectorFilterTypes.DOCX },
{ key: FilesSelectorFilterTypes.IMG, label: FilesSelectorFilterTypes.IMG },
{ key: FilesSelectorFilterTypes.BackupOnly, label: FilesSelectorFilterTypes.BackupOnly },
{ key: FilesSelectorFilterTypes.DOCXF, label: FilesSelectorFilterTypes.DOCXF },
{ key: FilesSelectorFilterTypes.XLSX, label: FilesSelectorFilterTypes.XLSX },
]);
const [widthDimension, setWidthDimension] = useState(dataDimensions[1]);
const [heightDimension, setHeightDimension] = useState(dataDimensions[0]);
const [width, setWidth] = useState("600");
const [height, setHeight] = useState("100");
const [isGetCodeDialogOpened, setIsGetCodeDialogOpened] = useState(false);
const [showPreview, setShowPreview] = useState(window.innerWidth > showPreviewThreshold);
const [sharedLinks, setSharedLinks] = useState(null);
const [selectedElementType, setSelectedElementType] = useState(elementDisplayOptions[1].value);
const [typeDisplay, setTypeDisplay] = useState(fileTypeDisplay[0].value);
const [selectedType, setSelectedType] = useState(fileOptions[0]);
const [selectedFileTypes, setSelectedFileTypes] = useState([
{ key: "file-type-documents", label: t("Common:Documents") },
{ key: "file-type-folders", label: t("Translations:Folders") },
{ key: "file-type-spreadsheets", label: t("Translations:Spreadsheets") },
{ key: "file-type-archives", label: t("Files:Archives") },
{ key: "file-type-presentations", label: t("Translations:Presentations") },
{ key: "file-type-images", label: t("Filse:Images") },
{ key: "file-type-media", label: t("Files:Media") },
{ key: "file-type-forms-templates", label: t("Files:FormsTemplates") },
{ key: "file-type-forms", label: t("Files:Forms") },
]);
const [config, setConfig] = useState({
mode: "file-selector",
width: `${width}${widthDimension.label}`,
height: `${height}${heightDimension.label}`,
frameId: "ds-frame",
init: true,
showSelectorCancel: true,
showSelectorHeader: true,
withSearch: true,
acceptButtonLabel: t("Common:SelectAction"),
cancelButtonLabel: t("Common:CancelButton"),
// withBreadCrumbs: true,
withSubtitle: true,
filterParam: FilesSelectorFilterTypes.ALL,
isButtonMode: false,
buttonWithLogo: true,
events: {
onSelectCallback: (items) => {
toastr.success(items[0].label);
},
onCloseCallback: null,
onAppReady: null,
onAppError: (e) => console.log("onAppError", e),
onEditorCloseCallback: null,
onAuthSuccess: null,
onSignOut: null,
},
});
const params = objectToGetParams(config);
const frameId = config.frameId || "ds-frame";
const destroyFrame = () => {
window.DocSpace?.SDK?.frames[frameId]?.destroyFrame();
};
const loadFrame = debounce(() => {
const script = document.getElementById("integration");
if (script) {
script.remove();
}
const params = objectToGetParams(config);
loadScript(`${scriptUrl}${params}`, "integration", () => window.DocSpace.SDK.initFrame(config));
}, 500);
useEffect(() => {
loadFrame();
return () => destroyFrame();
});
const toggleButtonMode = (e) => {
setSelectedElementType(e.target.value);
setConfig((config) => ({ ...config, isButtonMode: e.target.value === "button" }));
};
const onChangeTab = (tab) => {
if (tab.key === "preview" && selectedElementType === "button") {
setConfig((config) => ({ ...config, isButtonMode: true }));
} else if (tab.key === "selector-preview") {
setConfig((config) => ({ ...config, isButtonMode: false }));
} else if (tab.key === "code") {
setConfig((config) => ({ ...config, isButtonMode: selectedElementType === "button" }));
}
};
const onChangeWidth = (e) => {
setConfig((config) => {
return { ...config, width: `${e.target.value}${widthDimension.label}` };
});
setWidth(e.target.value);
};
const onChangeHeight = (e) => {
setConfig((config) => {
return { ...config, height: `${e.target.value}${heightDimension.label}` };
});
setHeight(e.target.value);
};
const onChangeFolderId = async (id, publicInPath) => {
let newConfig = { id, requestToken: null, rootPath: "/rooms/shared/" };
if (!!publicInPath) {
const links = await fetchExternalLinks(publicInPath.id);
if (links.length > 1) {
const linksOptions = links.map((link) => {
const { id, title, requestToken } = link.sharedTo;
return {
key: id,
label: title,
requestToken: requestToken,
};
});
setSharedLinks(linksOptions);
}
newConfig.requestToken = links[0].sharedTo?.requestToken;
newConfig.rootPath = "/rooms/share";
} else {
setSharedLinks(null);
}
setConfig((config) => {
return { ...config, ...newConfig };
});
};
const onChangeSharedLink = (link) => {
setConfig((config) => {
return { ...config, requestToken: link.requestToken };
});
};
const onChangeFrameId = (e) => {
setConfig((config) => {
return { ...config, frameId: e.target.value };
});
};
const changeColumnsOption = (e) => {
setTypeDisplay(e.target.value);
setConfig((config) => {
return {
...config,
filterParam:
e.target.value === FilesSelectorFilterTypes.ALL
? FilesSelectorFilterTypes.ALL
: selectedType,
};
});
};
const onChangeWidthDimension = (item) => {
setConfig((config) => {
return { ...config, width: `${width}${item.label}` };
});
setWidthDimension(item);
};
const onChangeHeightDimension = (item) => {
setConfig((config) => {
return { ...config, height: `${height}${item.label}` };
});
setHeightDimension(item);
};
const openGetCodeModal = () => setIsGetCodeDialogOpened(true);
const closeGetCodeModal = () => setIsGetCodeDialogOpened(false);
const onTypeSelect = (option) => {
// setFileOptions((prevFileOptions) => prevFileOptions.filter((file) => file.key !== option.key));
setSelectedType(option);
setConfig((config) => {
return { ...config, filterParam: option.key };
});
// if (!selectedFileTypes.find((type) => type.key === option.key)) {
// setSelectedFileTypes((prevFileTypes) => [...prevFileTypes, option]);
// }
};
const deleteSelectedType = (option) => {
setFileOptions((prevFileOptions) => [option, ...prevFileOptions]);
const filteredTypes = selectedFileTypes.filter((type) => type.key !== option.key);
setSelectedFileTypes(filteredTypes);
};
const toggleWithSearch = () => {
setConfig((config) => ({ ...config, withSearch: !config.withSearch }));
};
// const toggleBreadCrumbs = () => {
// setConfig((config) => ({ ...config, withBreadCrumbs: !config.withBreadCrumbs }));
// };
const toggleWithSubtitle = () => {
setConfig((config) => ({ ...config, withSubtitle: !config.withSubtitle }));
};
const onChangeAcceptLabel = (e) => {
setConfig((config) => {
return { ...config, acceptButtonLabel: e.target.value };
});
};
const onChangeCancelLabel = (e) => {
setConfig((config) => {
return { ...config, cancelButtonLabel: e.target.value };
});
};
const onResize = () => {
const isEnoughWidthForPreview = window.innerWidth > showPreviewThreshold;
if (isEnoughWidthForPreview !== showPreview) setShowPreview(isEnoughWidthForPreview);
};
const setButtonColor = (color) => {
setConfig((config) => ({ ...config, buttonColor: color }));
};
useEffect(() => {
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
}, [showPreview]);
const codeBlock = `<div id="${frameId}">Fallback text</div>\n<script src="${scriptUrl}${params}"></script>`;
const preview = (
<Frame
width={width + widthDimension.label}
height={height + heightDimension.label}
targetId={frameId}
>
<Box id={frameId}></Box>
</Frame>
);
const code = (
<CodeWrapper width={width + widthDimension.label} height={height + heightDimension.label}>
<CategorySubHeader className="copy-window-code">{t("CopyWindowCode")}</CategorySubHeader>
<Textarea value={codeBlock} heightTextArea={153} />
</CodeWrapper>
);
const dataTabs =
selectedElementType === "element"
? [
{
key: "preview",
title: t("Common:Preview"),
content: preview,
},
{
key: "code",
title: t("Code"),
content: code,
},
]
: [
{
key: "preview",
title: t("Common:Preview"),
content: preview,
},
{
key: "selector-preview",
title: t("SelectorPreview"),
content: preview,
},
{
key: "code",
title: t("Code"),
content: code,
},
];
return (
<SDKContainer>
<CategoryDescription>
<Text className="sdk-description">{t("FileSelectorPresetDescription")}</Text>
</CategoryDescription>
<CategoryHeader>{t("CreateSampleHeader")}</CategoryHeader>
<Container>
{showPreview && (
<Preview>
<TabsContainer onSelect={onChangeTab} elements={dataTabs} />
</Preview>
)}
<Controls>
<ControlsSection>
<CategorySubHeader>{t("MainElementParameter")}</CategorySubHeader>
<RadioButtonGroup
orientation="vertical"
options={elementDisplayOptions}
name="elementDisplayInput"
selected={selectedElementType}
onClick={toggleButtonMode}
spacing="8px"
/>
{config.isButtonMode && (
<>
<CategorySubHeader>{t("ButtonCustomization")}</CategorySubHeader>
<ControlsGroup>
<Label className="label" text={t("ButtonColor")} />
<ColorInput scale handleChange={setButtonColor} defaultColor={"#5299E0"} />
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("ButtonText")} />
<TextInput
scale
onChange={(e) => {
setConfig((config) => ({ ...config, buttonText: e.target.value }));
}}
placeholder={t("SelectToDocSpace")}
value={config.buttonText}
tabIndex={3}
/>
<Checkbox
className="checkbox"
label={"Logo"}
onChange={() => {
setConfig((config) => ({
...config,
buttonWithLogo: !config.buttonWithLogo,
}));
}}
isChecked={config.buttonWithLogo}
/>
</ControlsGroup>
</>
)}
</ControlsSection>
<ControlsSection>
<CategorySubHeader>{t("CustomizingDisplay")}</CategorySubHeader>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Width")} />
<RowContainer combo>
<TextInput
onChange={onChangeWidth}
placeholder={t("EnterWidth")}
value={width}
tabIndex={2}
/>
<ComboBox
size="content"
scaled={false}
scaledOptions={true}
onSelect={onChangeWidthDimension}
options={dataDimensions}
selectedOption={widthDimension}
displaySelectedOption
directionY="bottom"
/>
</RowContainer>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Height")} />
<RowContainer combo>
<TextInput
onChange={onChangeHeight}
placeholder={t("EnterHeight")}
value={height}
tabIndex={3}
/>
<ComboBox
size="content"
scaled={false}
scaledOptions={true}
onSelect={onChangeHeightDimension}
options={dataDimensions}
selectedOption={heightDimension}
displaySelectedOption
directionY="bottom"
/>
</RowContainer>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("FrameId")} />
<TextInput
scale={true}
onChange={onChangeFrameId}
placeholder={t("EnterId")}
value={config.frameId}
tabIndex={4}
/>
</ControlsGroup>
</ControlsSection>
<ControlsSection>
<Label className="label">{t("InterfaceElements")}</Label>
{/* <Checkbox
className="checkbox"
label={t("Common:Title")}
onChange={toggleBreadCrumbs}
isChecked={config.withBreadCrumbs}
/> */}
<LabelGroup>
<Checkbox
className="checkbox"
label={t("Subtitle")}
onChange={toggleWithSubtitle}
isChecked={config.withSubtitle}
/>
<HelpButton
place="right"
offsetRight={4}
size={12}
tooltipContent={
<TooltipContent
title={t("Subtitle")}
description={t("SubtitleDescription")}
img={SubtitleUrl}
/>
}
/>
</LabelGroup>
<LabelGroup>
<Checkbox
className="checkbox"
label={t("Common:Search")}
onChange={toggleWithSearch}
isChecked={config.withSearch}
/>
<HelpButton
place="right"
offsetRight={4}
size={12}
tooltipContent={
<TooltipContent
title={t("Common:Search")}
description={t("FilesSearchDescription")}
img={SearchUrl}
/>
}
/>
</LabelGroup>
<Label className="label" text={t("SelectButtonText")} />
<TextInput
scale={true}
onChange={onChangeAcceptLabel}
placeholder={t("Common:SelectAction")}
value={config.acceptButtonLabel}
tabIndex={4}
/>
<Label className="label" text={t("CancelButtonText")} />
<TextInput
scale={true}
onChange={onChangeCancelLabel}
placeholder={t("Common:CancelButton")}
value={config.cancelButtonLabel}
tabIndex={4}
/>
</ControlsSection>
<ControlsSection>
<CategorySubHeader>{t("DataDisplay")}</CategorySubHeader>
<ControlsGroup>
<LabelGroup>
<Label className="label" text={t("RoomOrFolder")} />
<HelpButton
offsetRight={0}
size={12}
tooltipContent={<Text fontSize="12px">{t("RoomOrFolderDescription")}</Text>}
/>
</LabelGroup>
<FilesSelectorInputWrapper>
<FilesSelectorInput onSelectFolder={onChangeFolderId} isSelect />
</FilesSelectorInputWrapper>
</ControlsGroup>
{sharedLinks && (
<ControlsGroup>
<LabelGroup>
<Label className="label" text={t("SharingPanel:ExternalLink")} />
<HelpButton
offsetRight={0}
size={12}
tooltipContent={
<Text fontSize="12px">{t("CreateEditRoomDialog:PublicRoomDescription")}</Text>
}
/>
</LabelGroup>
<ComboBox
scaled={true}
onSelect={onChangeSharedLink}
options={sharedLinks}
selectedOption={sharedLinks[0]}
displaySelectedOption
directionY="bottom"
/>
</ControlsGroup>
)}
</ControlsSection>
<ControlsSection>
<CategorySubHeader>{t("AdvancedDisplay")}</CategorySubHeader>
<Label className="label" text={t("FileTypeDisplay")} />
<RadioButtonGroup
orientation="vertical"
options={fileTypeDisplay}
name="columnsDisplayOptions"
selected={typeDisplay}
onClick={changeColumnsOption}
spacing="8px"
/>
{typeDisplay === "custom-types" && (
<>
<ComboBox
onSelect={onTypeSelect}
options={
fileOptions || {
key: "Select",
label: t("Common:SelectAction"),
}
}
scaled={true}
directionY="top"
selectedOption={selectedType}
// selectedOption={{
// key: "Select",
// label: t("Common:SelectAction"),
// }}
/>
{/* <SelectedItemsContainer>
{selectedFileTypes.map((type) => (
<SelectedItem
key={type.key}
onClick={() => deleteSelectedType(type)}
label={type.label}
/>
))}
</SelectedItemsContainer> */}
</>
)}
</ControlsSection>
</Controls>
</Container>
{!showPreview && (
<>
<GetCodeButtonWrapper>
<Button
id="get-sdk-code-button"
primary
size="normal"
scale
label={t("GetCode")}
onClick={openGetCodeModal}
/>
</GetCodeButtonWrapper>
<GetCodeDialog
t={t}
visible={isGetCodeDialogOpened}
codeBlock={codeBlock}
onClose={closeGetCodeModal}
/>
</>
)}
</SDKContainer>
);
};
export default inject(({ authStore, settingsStore, publicRoomStore }) => {
const { setDocumentTitle } = authStore;
const { theme } = settingsStore;
const { fetchExternalLinks } = publicRoomStore;
return {
theme,
setDocumentTitle,
fetchExternalLinks,
};
})(
withTranslation([
"JavascriptSdk",
"Files",
"EmbeddingPanel",
"Common",
"Translations",
"SharingPanel",
])(observer(FileSelector)),
);

View File

@ -0,0 +1,483 @@
import { useState, useEffect } from "react";
import { withTranslation } from "react-i18next";
import debounce from "lodash.debounce";
import { Box } from "@docspace/shared/components/box";
import { TextInput } from "@docspace/shared/components/text-input";
import { Textarea } from "@docspace/shared/components/textarea";
import { Label } from "@docspace/shared/components/label";
import { Text } from "@docspace/shared/components/text";
import { Checkbox } from "@docspace/shared/components/checkbox";
import { ComboBox } from "@docspace/shared/components/combobox";
import { TabsContainer } from "@docspace/shared/components/tabs-container";
import { RadioButtonGroup } from "@docspace/shared/components/radio-button-group";
import { ColorInput } from "@docspace/shared/components/color-input";
import { objectToGetParams, loadScript } from "@docspace/shared/utils/common";
import { inject, observer } from "mobx-react";
import { isTablet, isMobile } from "@docspace/shared/utils/device";
import GetCodeDialog from "../sub-components/GetCodeDialog";
import { Button } from "@docspace/shared/components/button";
import { RoomsType } from "@docspace/shared/enums";
import { toastr } from "@docspace/shared/components/toast";
const showPreviewThreshold = 720;
import {
SDKContainer,
Controls,
CategoryHeader,
CategorySubHeader,
CategoryDescription,
ControlsGroup,
ControlsSection,
Frame,
Container,
RowContainer,
Preview,
GetCodeButtonWrapper,
CodeWrapper,
} from "./StyledPresets";
const RoomSelector = (props) => {
const { t, setDocumentTitle } = props;
setDocumentTitle(t("JavascriptSdk"));
const scriptUrl = `${window.location.origin}/static/scripts/api.js`;
const dataDimensions = [
{ key: "percent", label: "%", default: true },
{ key: "pixel", label: "px" },
];
const elementDisplayOptions = [
{ value: "element", label: t("ElementItself") },
{
value: "button",
label: (
<RowContainer>
{t("Common:Button")}
<Text color="gray">{`(${t("ElementCalledAfterClicking")})`}</Text>
</RowContainer>
),
},
];
const roomTypeOptions = [
{ key: "room-type-all", label: t("AllTypes"), roomType: undefined, default: true },
{
key: "room-type-collaboration",
label: t("CreateEditRoomDialog:CollaborationRoomTitle"),
roomType: RoomsType.EditingRoom,
},
{ key: "room-type-public", label: t("Files:PublicRoom"), roomType: RoomsType.PublicRoom },
{
key: "room-type-custom",
label: t("CreateEditRoomDialog:CustomRoomTitle"),
roomType: RoomsType.CustomRoom,
},
];
const [widthDimension, setWidthDimension] = useState(dataDimensions[1]);
const [heightDimension, setHeightDimension] = useState(dataDimensions[0]);
const [width, setWidth] = useState("600");
const [height, setHeight] = useState("100");
const [isGetCodeDialogOpened, setIsGetCodeDialogOpened] = useState(false);
const [showPreview, setShowPreview] = useState(window.innerWidth > showPreviewThreshold);
const [selectedElementType, setSelectedElementType] = useState(elementDisplayOptions[0].value);
const [roomType, setRoomType] = useState(roomTypeOptions[0]);
const debouncedOnSelect = debounce((items) => {
toastr.success(items[0].label);
}, 0);
const [config, setConfig] = useState({
mode: "room-selector",
width: `${width}${widthDimension.label}`,
height: `${height}${heightDimension.label}`,
frameId: "ds-frame",
init: true,
showSelectorCancel: true,
showSelectorHeader: true,
withSearch: true,
acceptButtonLabel: t("Common:SelectAction"),
cancelButtonLabel: t("Common:CancelButton"),
isButtonMode: false,
buttonWithLogo: true,
events: {
onSelectCallback: debouncedOnSelect,
onCloseCallback: null,
onAppReady: null,
onAppError: (e) => console.log("onAppError", e),
onEditorCloseCallback: null,
onAuthSuccess: null,
onSignOut: null,
},
});
const params = objectToGetParams(config);
const frameId = config.frameId || "ds-frame";
const destroyFrame = () => {
window.DocSpace?.SDK?.frames[frameId]?.destroyFrame();
};
const loadFrame = debounce(() => {
const script = document.getElementById("integration");
if (script) {
script.remove();
}
const params = objectToGetParams(config);
loadScript(`${scriptUrl}${params}`, "integration", () => window.DocSpace.SDK.initFrame(config));
}, 500);
useEffect(() => {
loadFrame();
return destroyFrame;
});
const toggleButtonMode = (e) => {
setSelectedElementType(e.target.value);
setConfig((config) => ({ ...config, isButtonMode: e.target.value === "button" }));
};
const changeRoomType = (option) => {
setRoomType(option);
setConfig((config) => ({ ...config, roomType: option.roomType }));
};
const onChangeTab = (tab) => {
if (tab.key === "preview" && selectedElementType === "button") {
setConfig((config) => ({ ...config, isButtonMode: true }));
} else if (tab.key === "selector-preview") {
setConfig((config) => ({ ...config, isButtonMode: false }));
} else if (tab.key === "code") {
setConfig((config) => ({ ...config, isButtonMode: selectedElementType === "button" }));
}
};
const onChangeWidth = (e) => {
setConfig((config) => {
return { ...config, width: `${e.target.value}${widthDimension.label}` };
});
setWidth(e.target.value);
};
const onChangeHeight = (e) => {
setConfig((config) => {
return { ...config, height: `${e.target.value}${heightDimension.label}` };
});
setHeight(e.target.value);
};
const onChangeFrameId = (e) => {
setConfig((config) => {
return { ...config, frameId: e.target.value };
});
};
const onChangeWidthDimension = (item) => {
setConfig((config) => {
return { ...config, width: `${width}${item.label}` };
});
setWidthDimension(item);
};
const onChangeHeightDimension = (item) => {
setConfig((config) => {
return { ...config, height: `${height}${item.label}` };
});
setHeightDimension(item);
};
const toggleWithSearch = () => {
setConfig((config) => ({ ...config, withSearch: !config.withSearch }));
};
const onChangeAcceptLabel = (e) => {
setConfig((config) => {
return { ...config, acceptButtonLabel: e.target.value };
});
};
const onChangeCancelLabel = (e) => {
setConfig((config) => {
return { ...config, cancelButtonLabel: e.target.value };
});
};
const openGetCodeModal = () => setIsGetCodeDialogOpened(true);
const closeGetCodeModal = () => setIsGetCodeDialogOpened(false);
const onResize = () => {
const isEnoughWidthForPreview = window.innerWidth > showPreviewThreshold;
if (isEnoughWidthForPreview !== showPreview) setShowPreview(isEnoughWidthForPreview);
};
const setButtonColor = (color) => {
setConfig((config) => ({ ...config, buttonColor: color }));
};
useEffect(() => {
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
}, [showPreview]);
const codeBlock = `<div id="${frameId}">Fallback text</div>\n<script src="${scriptUrl}${params}"></script>`;
const preview = (
<Frame
width={width + widthDimension.label}
height={height + heightDimension.label}
targetId={frameId}
>
<Box id={frameId}></Box>
</Frame>
);
const code = (
<CodeWrapper width={width + widthDimension.label} height={height + heightDimension.label}>
<CategorySubHeader className="copy-window-code">{t("CopyWindowCode")}</CategorySubHeader>
<Textarea value={codeBlock} heightTextArea={153} />
</CodeWrapper>
);
const dataTabs =
selectedElementType === "element"
? [
{
key: "preview",
title: t("Common:Preview"),
content: preview,
},
{
key: "code",
title: t("Code"),
content: code,
},
]
: [
{
key: "preview",
title: t("Common:Preview"),
content: preview,
},
{
key: "selector-preview",
title: t("SelectorPreview"),
content: preview,
},
{
key: "code",
title: t("Code"),
content: code,
},
];
return (
<SDKContainer>
<CategoryDescription>
<Text className="sdk-description">{t("RoomSelectorPresetDescription")}</Text>
</CategoryDescription>
<CategoryHeader>{t("CreateSampleHeader")}</CategoryHeader>
<Container>
{showPreview && (
<Preview>
<TabsContainer onSelect={onChangeTab} elements={dataTabs} />
</Preview>
)}
<Controls>
<ControlsSection>
<CategorySubHeader>{t("MainElementParameter")}</CategorySubHeader>
<RadioButtonGroup
orientation="vertical"
options={elementDisplayOptions}
name="elementDisplayInput"
selected={selectedElementType}
onClick={toggleButtonMode}
spacing="8px"
/>
{config.isButtonMode && (
<>
<CategorySubHeader>{t("ButtonCustomization")}</CategorySubHeader>
<ControlsGroup>
<Label className="label" text={t("ButtonColor")} />
<ColorInput scale handleChange={setButtonColor} defaultColor={"#5299E0"} />
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("ButtonText")} />
<TextInput
scale
onChange={(e) => {
setConfig((config) => ({ ...config, buttonText: e.target.value }));
}}
placeholder={t("SelectToDocSpace")}
value={config.buttonText}
tabIndex={3}
/>
<Checkbox
className="checkbox"
label={"Logo"}
onChange={() => {
setConfig((config) => ({
...config,
buttonWithLogo: !config.buttonWithLogo,
}));
}}
isChecked={config.buttonWithLogo}
/>
</ControlsGroup>
</>
)}
</ControlsSection>
<ControlsSection>
<CategorySubHeader>{t("CustomizingDisplay")}</CategorySubHeader>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Width")} />
<RowContainer combo>
<TextInput
onChange={onChangeWidth}
placeholder={t("EnterWidth")}
value={width}
tabIndex={4}
/>
<ComboBox
size="content"
scaled={false}
scaledOptions={true}
onSelect={onChangeWidthDimension}
options={dataDimensions}
selectedOption={widthDimension}
displaySelectedOption
directionY="bottom"
/>
</RowContainer>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Height")} />
<RowContainer combo>
<TextInput
onChange={onChangeHeight}
placeholder={t("EnterHeight")}
value={height}
tabIndex={5}
/>
<ComboBox
size="content"
scaled={false}
scaledOptions={true}
onSelect={onChangeHeightDimension}
options={dataDimensions}
selectedOption={heightDimension}
displaySelectedOption
directionY="bottom"
/>
</RowContainer>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("FrameId")} />
<TextInput
scale={true}
onChange={onChangeFrameId}
placeholder={t("EnterId")}
value={config.frameId}
tabIndex={6}
/>
</ControlsGroup>
</ControlsSection>
<ControlsSection>
<Label className="label">{t("InterfaceElements")}</Label>
<Checkbox
className="checkbox"
label={t("Common:Search")}
onChange={toggleWithSearch}
isChecked={config.withSearch}
/>
<Label className="label" text={t("SelectButtonText")} />
<TextInput
scale={true}
onChange={onChangeAcceptLabel}
placeholder={t("Common:SelectAction")}
value={config.acceptButtonLabel}
tabIndex={7}
/>
<Label className="label" text={t("CancelButtonText")} />
<TextInput
scale={true}
onChange={onChangeCancelLabel}
placeholder={t("Common:CancelButton")}
value={config.cancelButtonLabel}
tabIndex={8}
/>
</ControlsSection>
<ControlsSection>
<CategorySubHeader>{t("AdvancedDisplay")}</CategorySubHeader>
<Label className="label" text={t("RoomTypeDisplay")} />
<ComboBox
onSelect={changeRoomType}
options={roomTypeOptions}
scaled={true}
selectedOption={roomType}
displaySelectedOption
directionY="top"
/>
</ControlsSection>
</Controls>
</Container>
{!showPreview && (
<>
<GetCodeButtonWrapper>
<Button
id="get-sdk-code-button"
primary
size="normal"
scale
label={t("GetCode")}
onClick={openGetCodeModal}
/>
</GetCodeButtonWrapper>
<GetCodeDialog
t={t}
visible={isGetCodeDialogOpened}
codeBlock={codeBlock}
onClose={closeGetCodeModal}
/>
</>
)}
</SDKContainer>
);
};
export default inject(({ authStore, settingsStore }) => {
const { setDocumentTitle } = authStore;
const { theme } = settingsStore;
return {
theme,
setDocumentTitle,
};
})(
withTranslation(["JavascriptSdk", "Files", "EmbeddingPanel", "Common", "CreateEditRoomDialog"])(
observer(RoomSelector),
),
);

View File

@ -0,0 +1,407 @@
import { useState, useEffect } from "react";
import { withTranslation } from "react-i18next";
import debounce from "lodash.debounce";
import { Box } from "@docspace/shared/components/box";
import { TextInput } from "@docspace/shared/components/text-input";
import { Textarea } from "@docspace/shared/components/textarea";
import { Label } from "@docspace/shared/components/label";
import { Text } from "@docspace/shared/components/text";
import { ComboBox } from "@docspace/shared/components/combobox";
import { TabsContainer } from "@docspace/shared/components/tabs-container";
import FilesSelectorInput from "SRC_DIR/components/FilesSelectorInput";
import { objectToGetParams, loadScript } from "@docspace/shared/utils/common";
import { inject, observer } from "mobx-react";
import { isTablet, isMobile } from "@docspace/shared/utils/device";
import { HelpButton } from "@docspace/shared/components/help-button";
import GetCodeDialog from "../sub-components/GetCodeDialog";
import { Button } from "@docspace/shared/components/button";
import EmptyIframeContainer from "../sub-components/EmptyIframeContainer";
const showPreviewThreshold = 720;
import {
SDKContainer,
Controls,
CategoryHeader,
CategorySubHeader,
CategoryDescription,
ControlsGroup,
LabelGroup,
Frame,
Container,
RowContainer,
Preview,
GetCodeButtonWrapper,
FilesSelectorInputWrapper,
ControlsSection,
CodeWrapper,
} from "./StyledPresets";
const SimpleRoom = (props) => {
const { t, setDocumentTitle, fetchExternalLinks } = props;
setDocumentTitle(t("JavascriptSdk"));
const scriptUrl = `${window.location.origin}/static/scripts/api.js`;
const dataDimensions = [
{ key: "percent", label: "%", default: true },
{ key: "pixel", label: "px" },
];
const [widthDimension, setWidthDimension] = useState(dataDimensions[0]);
const [heightDimension, setHeightDimension] = useState(dataDimensions[1]);
const [width, setWidth] = useState("100");
const [height, setHeight] = useState(isTablet() ? "400" : isMobile() ? "206" : "600");
const [isGetCodeDialogOpened, setIsGetCodeDialogOpened] = useState(false);
const [showPreview, setShowPreview] = useState(window.innerWidth > showPreviewThreshold);
const [sharedLinks, setSharedLinks] = useState(null);
const [config, setConfig] = useState({
mode: "manager",
width: `${width}${widthDimension.label}`,
height: `${height}${heightDimension.label}`,
frameId: "ds-frame",
showHeader: false,
showTitle: true,
showMenu: false,
showFilter: true,
disableActionButton: false,
infoPanelVisible: false,
init: false,
filter: {
count: 100,
page: 1,
sortorder: "descending",
sortby: "DateAndTime",
search: "",
withSubfolders: false,
},
});
const params = objectToGetParams(config);
const frameId = config.frameId || "ds-frame";
const destroyFrame = () => {
window.DocSpace?.SDK?.frames[frameId]?.destroyFrame();
};
const loadFrame = debounce(() => {
const script = document.getElementById("integration");
if (script) {
script.remove();
}
const params = objectToGetParams(config);
loadScript(`${scriptUrl}${params}`, "integration", () => window.DocSpace.SDK.initFrame(config));
}, 500);
useEffect(() => {
loadFrame();
return () => destroyFrame();
});
const onChangeTab = () => {
loadFrame();
};
const onChangeWidth = (e) => {
setConfig((config) => {
return { ...config, width: `${e.target.value}${widthDimension.label}` };
});
setWidth(e.target.value);
};
const onChangeHeight = (e) => {
setConfig((config) => {
return { ...config, height: `${e.target.value}${heightDimension.label}` };
});
setHeight(e.target.value);
};
const onChangeFolderId = async (id, publicInPath) => {
let newConfig = { id, requestToken: null, rootPath: "/rooms/shared/" };
if (!!publicInPath) {
const links = await fetchExternalLinks(publicInPath.id);
if (links.length > 1) {
const linksOptions = links.map((link) => {
const { id, title, requestToken } = link.sharedTo;
return {
key: id,
label: title,
requestToken: requestToken,
};
});
setSharedLinks(linksOptions);
}
newConfig.requestToken = links[0].sharedTo?.requestToken;
newConfig.rootPath = "/rooms/share";
} else {
setSharedLinks(null);
}
setConfig((config) => {
return { ...config, ...newConfig, init: true };
});
};
const onChangeSharedLink = (link) => {
setConfig((config) => {
return { ...config, requestToken: link.requestToken };
});
};
const onChangeFrameId = (e) => {
setConfig((config) => {
return { ...config, frameId: e.target.value };
});
};
const onChangeWidthDimension = (item) => {
setConfig((config) => {
return { ...config, width: `${width}${item.label}` };
});
setWidthDimension(item);
};
const onChangeHeightDimension = (item) => {
setConfig((config) => {
return { ...config, height: `${height}${item.label}` };
});
setHeightDimension(item);
};
const openGetCodeModal = () => setIsGetCodeDialogOpened(true);
const closeGetCodeModal = () => setIsGetCodeDialogOpened(false);
const onResize = () => {
const isEnoughWidthForPreview = window.innerWidth > showPreviewThreshold;
if (isEnoughWidthForPreview !== showPreview) setShowPreview(isEnoughWidthForPreview);
};
useEffect(() => {
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
}, [showPreview]);
const codeBlock = `<div id="${frameId}">Fallback text</div>\n<script src="${scriptUrl}${params}"></script>`;
const preview = (
<Frame
width={width + widthDimension.label}
height={height + heightDimension.label}
targetId={frameId}
>
{config.id !== undefined ? (
<>
<Box id={frameId}></Box>
</>
) : (
<EmptyIframeContainer
text={t("SelectRoom")}
width={width + widthDimension.label}
height={height + heightDimension.label}
/>
)}
</Frame>
);
const code = (
<CodeWrapper>
<CategorySubHeader className="copy-window-code">{t("CopyWindowCode")}</CategorySubHeader>
<Textarea value={codeBlock} heightTextArea={153} />
</CodeWrapper>
);
const dataTabs = [
{
key: "preview",
title: t("Common:Preview"),
content: preview,
},
{
key: "code",
title: t("Code"),
content: code,
},
];
return (
<SDKContainer>
<CategoryDescription>
<Text className="sdk-description">{t("SimpleRoomPresetDescription")}</Text>
</CategoryDescription>
<CategoryHeader>{t("CreateSampleHeader")}</CategoryHeader>
<Container>
{showPreview && (
<Preview>
<TabsContainer
isDisabled={config?.id === undefined}
onSelect={onChangeTab}
elements={dataTabs}
/>
</Preview>
)}
<Controls>
<ControlsSection>
<CategorySubHeader>{t("DataDisplay")}</CategorySubHeader>
<ControlsGroup>
<LabelGroup>
<Label className="label" text={t("Common:Room")} />
<HelpButton
offsetRight={0}
size={12}
tooltipContent={<Text fontSize="12px">{t("RoomOrFolderDescription")}</Text>}
/>
</LabelGroup>
<FilesSelectorInputWrapper>
<FilesSelectorInput onSelectFolder={onChangeFolderId} isSelect isRoomsOnly />
</FilesSelectorInputWrapper>
</ControlsGroup>
{sharedLinks && (
<ControlsGroup>
<LabelGroup>
<Label className="label" text={t("SharingPanel:ExternalLink")} />
<HelpButton
offsetRight={0}
size={12}
tooltipContent={
<Text fontSize="12px">{t("CreateEditRoomDialog:PublicRoomDescription")}</Text>
}
/>
</LabelGroup>
<ComboBox
scaled={true}
onSelect={onChangeSharedLink}
options={sharedLinks}
selectedOption={sharedLinks[0]}
displaySelectedOption
directionY="bottom"
/>
</ControlsGroup>
)}
</ControlsSection>
<ControlsSection>
<CategorySubHeader>{t("CustomizingDisplay")}</CategorySubHeader>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Width")} />
<RowContainer combo>
<TextInput
onChange={onChangeWidth}
placeholder={t("EnterWidth")}
value={width}
tabIndex={2}
/>
<ComboBox
size="content"
scaled={false}
scaledOptions={true}
onSelect={onChangeWidthDimension}
options={dataDimensions}
selectedOption={widthDimension}
displaySelectedOption
directionY="bottom"
/>
</RowContainer>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Height")} />
<RowContainer combo>
<TextInput
onChange={onChangeHeight}
placeholder={t("EnterHeight")}
value={height}
tabIndex={3}
/>
<ComboBox
size="content"
scaled={false}
scaledOptions={true}
onSelect={onChangeHeightDimension}
options={dataDimensions}
selectedOption={heightDimension}
displaySelectedOption
directionY="bottom"
/>
</RowContainer>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("FrameId")} />
<TextInput
scale={true}
onChange={onChangeFrameId}
placeholder={t("EnterId")}
value={config.frameId}
tabIndex={4}
/>
</ControlsGroup>
</ControlsSection>
</Controls>
</Container>
{!showPreview && (
<>
<GetCodeButtonWrapper>
<Button
id="get-sdk-code-button"
primary
size="normal"
scale
label={t("GetCode")}
onClick={openGetCodeModal}
/>
</GetCodeButtonWrapper>
<GetCodeDialog
t={t}
visible={isGetCodeDialogOpened}
codeBlock={codeBlock}
onClose={closeGetCodeModal}
/>
</>
)}
</SDKContainer>
);
};
export default inject(({ authStore, settingsStore, publicRoomStore }) => {
const { setDocumentTitle } = authStore;
const { theme } = settingsStore;
const { fetchExternalLinks } = publicRoomStore;
return {
theme,
setDocumentTitle,
fetchExternalLinks,
};
})(
withTranslation([
"JavascriptSdk",
"Files",
"EmbeddingPanel",
"Common",
"Files",
"Translations",
"SharingPanel",
])(observer(SimpleRoom)),
);

View File

@ -0,0 +1,264 @@
import styled, { css } from "styled-components";
import { isMobile, mobile, tablet } from "@docspace/shared/utils/device";
import { Box } from "@docspace/shared/components/box";
import Base from "@docspace/shared/themes/base";
export const SDKContainer = styled(Box)`
@media ${tablet} {
width: 100%;
}
${isMobile() &&
css`
width: 100%;
`}
.tabs_body {
height: calc(100lvh - 260px);
}
`;
export const Controls = styled(Box)`
max-width: 350px;
min-width: 350px;
width: 100%;
display: flex;
flex-direction: column;
gap: 32px;
margin-top: 16px;
@media ${tablet} {
min-width: 0;
}
${isMobile() &&
css`
min-width: 0;
`}
.label {
min-width: fit-content;
}
.checkbox {
max-width: fit-content;
}
`;
export const CategoryHeader = styled.div`
margin-top: 24px;
margin-bottom: 24px;
font-size: ${(props) => props.theme.getCorrectFontSize("16px")};
font-style: normal;
font-weight: 700;
line-height: 22px;
@media ${tablet} {
margin-top: 24px;
}
${isMobile() &&
css`
margin-top: 24px;
`}
`;
export const CategorySubHeader = styled.div`
font-size: ${(props) => props.theme.getCorrectFontSize("15px")};
font-style: normal;
font-weight: 600;
line-height: 16px;
@media ${tablet} {
&:not(&.copy-window-code) {
margin-bottom: 0;
}
}
${isMobile() &&
css`
&:not(&.copy-window-code) {
margin-bottom: 0;
}
`}
@media ${mobile} {
&:first-of-type {
margin-top: 0;
}
}
`;
export const CategoryDescription = styled(Box)`
max-width: 700px;
.sdk-description {
line-height: 20px;
color: ${(props) => props.theme.client.settings.common.descriptionColor};
}
`;
export const ControlsGroup = styled(Box)`
display: flex;
flex-direction: column;
gap: 4px;
.toggle {
position: relative;
}
@media ${tablet} {
gap: 4px;
}
${isMobile() &&
css`
gap: 4px;
`}
`;
export const CheckboxGroup = styled(Box)`
display: flex;
flex-direction: column;
gap: 10px;
`;
export const LabelGroup = styled(Box)`
display: inline-flex;
align-items: center;
gap: 4px;
`;
export const ControlsSection = styled(Box)`
display: flex;
flex-direction: column;
gap: 16px;
`;
export const Frame = styled(Box)`
margin-top: 16px;
position: relative;
border-radius: 6px;
border: 1px solid ${(props) => props.theme.sdkPresets.borderColor};
width: calc(${(props) => (props.width ? props.width : "100%")} + 2px);
height: calc(${(props) => (props.height ? props.height : "400px")} + 2px);
display: flex;
align-items: center;
justify-content: center;
@media ${tablet} {
margin-top: 4px;
}
${(props) =>
props.targetId &&
`
#${props.targetId} {
border-radius: 6px;
}
`}
${isMobile() &&
css`
margin-top: 4px;
`}
.frame-container {
height: 100% !important;
& > iframe {
height: 100% !important;
}
}
`;
Frame.defaultProps = { theme: Base };
export const Container = styled(Box)`
width: 100%;
display: flex;
flex-direction: row-reverse;
justify-content: flex-end;
gap: 48px;
@media ${tablet} {
flex-direction: column;
}
${isMobile() &&
css`
flex-direction: column;
`}
`;
export const RowContainer = styled(Box)`
flex-direction: row;
display: flex;
gap: 8px;
${(props) =>
props.combo &&
`
height: 32px;
align-items: center;
`}
`;
export const ColumnContainer = styled(Box)`
flex-direction: column;
display: flex;
gap: 8px;
`;
export const Preview = styled(Box)`
width: 100%;
min-width: 660px;
flex-direction: row;
@media ${tablet} {
margin-top: 0;
min-width: 0;
}
${isMobile() &&
css`
margin-top: 0;
min-width: 0;
`}
`;
export const GetCodeButtonWrapper = styled.div`
padding-block: 30px;
position: sticky;
bottom: 0;
margin-top: 32px;
background-color: ${({ theme }) => theme.backgroundColor};
@media ${mobile} {
position: fixed;
padding-inline: 16px;
inset-inline: 0;
}
`;
export const FilesSelectorInputWrapper = styled.div`
& > div {
margin: 0;
}
`;
export const SelectedItemsContainer = styled.div`
display: flex;
flex-wrap: wrap;
`;
export const CodeWrapper = styled.div`
display: flex;
flex-direction: column;
gap: 16px;
margin-top: 16px;
width: calc(${(props) => (props.width ? props.width : "100%")} + 2px);
height: calc(${(props) => (props.height ? props.height : "400px")} + 2px);
`;

View File

@ -0,0 +1,395 @@
import { useState, useEffect } from "react";
import { withTranslation } from "react-i18next";
import debounce from "lodash.debounce";
import { Box } from "@docspace/shared/components/box";
import { TextInput } from "@docspace/shared/components/text-input";
import { Textarea } from "@docspace/shared/components/textarea";
import { Label } from "@docspace/shared/components/label";
import { Text } from "@docspace/shared/components/text";
import { Checkbox } from "@docspace/shared/components/checkbox";
import { ComboBox } from "@docspace/shared/components/combobox";
import { TabsContainer } from "@docspace/shared/components/tabs-container";
import FilesSelectorInput from "SRC_DIR/components/FilesSelectorInput";
import { objectToGetParams, loadScript } from "@docspace/shared/utils/common";
import { inject, observer } from "mobx-react";
import { ImageEditor } from "@docspace/shared/components/image-editor";
import { FilesSelectorFilterTypes } from "@docspace/shared/enums";
import { isTablet, isMobile } from "@docspace/shared/utils/device";
import EmptyIframeContainer from "../sub-components/EmptyIframeContainer";
import GetCodeDialog from "../sub-components/GetCodeDialog";
import { Button } from "@docspace/shared/components/button";
const showPreviewThreshold = 720;
import {
SDKContainer,
Controls,
CategoryHeader,
CategorySubHeader,
CategoryDescription,
ControlsGroup,
LabelGroup,
ControlsSection,
Frame,
Container,
RowContainer,
ColumnContainer,
Preview,
GetCodeButtonWrapper,
FilesSelectorInputWrapper,
CodeWrapper,
} from "./StyledPresets";
const Viewer = (props) => {
const { t, setDocumentTitle } = props;
setDocumentTitle(t("JavascriptSdk"));
const scriptUrl = `${window.location.origin}/static/scripts/api.js`;
const dataDimensions = [
{ key: "percent", label: "%", default: true },
{ key: "pixel", label: "px" },
];
const [widthDimension, setWidthDimension] = useState(dataDimensions[0]);
const [heightDimension, setHeightDimension] = useState(dataDimensions[0]);
const [width, setWidth] = useState("100");
const [height, setHeight] = useState("100");
const [isGetCodeDialogOpened, setIsGetCodeDialogOpened] = useState(false);
const [showPreview, setShowPreview] = useState(window.innerWidth > showPreviewThreshold);
const [config, setConfig] = useState({
mode: "viewer",
width: `${width}${widthDimension.label}`,
height: `${height}${heightDimension.label}`,
frameId: "ds-frame",
init: false,
});
const params = objectToGetParams(config);
const frameId = config.frameId || "ds-frame";
const destroyFrame = () => {
window.DocSpace?.SDK?.frames[frameId]?.destroyFrame();
};
const loadFrame = debounce(() => {
const script = document.getElementById("integration");
if (script) {
script.remove();
}
const params = objectToGetParams(config);
loadScript(`${scriptUrl}${params}`, "integration", () => window.DocSpace.SDK.initFrame(config));
}, 500);
useEffect(() => {
loadFrame();
return () => destroyFrame();
});
const onChangeTab = () => {
loadFrame();
};
const onChangeWidth = (e) => {
setConfig((config) => {
return { ...config, width: `${e.target.value}${widthDimension.label}` };
});
setWidth(e.target.value);
};
const onChangeHeight = (e) => {
setConfig((config) => {
return { ...config, height: `${e.target.value}${heightDimension.label}` };
});
setHeight(e.target.value);
};
const onChangeFileId = (file) => {
setConfig((config) => {
return { ...config, id: file.id };
});
};
const onChangeFrameId = (e) => {
setConfig((config) => {
return { ...config, frameId: e.target.value, init: true };
});
};
const onChangeWidthDimension = (item) => {
setConfig((config) => {
return { ...config, width: `${width}${item.label}` };
});
setWidthDimension(item);
};
const onChangeHeightDimension = (item) => {
setConfig((config) => {
return { ...config, height: `${height}${item.label}` };
});
setHeightDimension(item);
};
const openGetCodeModal = () => setIsGetCodeDialogOpened(true);
const closeGetCodeModal = () => setIsGetCodeDialogOpened(false);
const onResize = () => {
const isEnoughWidthForPreview = window.innerWidth > showPreviewThreshold;
if (isEnoughWidthForPreview !== showPreview) setShowPreview(isEnoughWidthForPreview);
};
useEffect(() => {
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
}, [showPreview]);
const codeBlock = `<div id="${frameId}">Fallback text</div>\n<script src="${scriptUrl}${params}"></script>`;
const preview = (
<Frame
width={width + widthDimension.label}
height={height + heightDimension.label}
targetId={frameId}
>
{config.id !== undefined ? (
<>
<Box id={frameId}></Box>
</>
) : (
<EmptyIframeContainer
text={t("SelectFile")}
width={width + widthDimension.label}
height={height + heightDimension.label}
/>
)}
</Frame>
);
const code = (
<CodeWrapper>
<CategorySubHeader className="copy-window-code">{t("CopyWindowCode")}</CategorySubHeader>
<Textarea value={codeBlock} heightTextArea={153} />
</CodeWrapper>
);
const dataTabs = [
{
key: "preview",
title: t("Common:Preview"),
content: preview,
},
{
key: "code",
title: t("Code"),
content: code,
},
];
return (
<SDKContainer>
<CategoryDescription>
<Text className="sdk-description">{t("ViewerPresetDescription")}</Text>
</CategoryDescription>
<CategoryHeader>{t("CreateSampleHeader")}</CategoryHeader>
<Container>
{showPreview && (
<Preview>
<TabsContainer
isDisabled={config?.id === undefined}
onSelect={onChangeTab}
elements={dataTabs}
/>
</Preview>
)}
<Controls>
<ControlsSection>
<CategorySubHeader>{t("FileId")}</CategorySubHeader>
<ControlsGroup>
<LabelGroup>
<Label className="label" text={t("Common:SelectFile")} />
</LabelGroup>
<FilesSelectorInputWrapper>
<FilesSelectorInput
onSelectFile={onChangeFileId}
filterParam={FilesSelectorFilterTypes.ALL}
isSelect
/>
</FilesSelectorInputWrapper>
</ControlsGroup>
</ControlsSection>
<ControlsSection>
<CategorySubHeader>{t("CustomizingDisplay")}</CategorySubHeader>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Width")} />
<RowContainer combo>
<TextInput
onChange={onChangeWidth}
placeholder={t("EnterWidth")}
value={width}
tabIndex={2}
/>
<ComboBox
size="content"
scaled={false}
scaledOptions={true}
onSelect={onChangeWidthDimension}
options={dataDimensions}
selectedOption={widthDimension}
displaySelectedOption
directionY="bottom"
/>
</RowContainer>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Height")} />
<RowContainer combo>
<TextInput
onChange={onChangeHeight}
placeholder={t("EnterHeight")}
value={height}
tabIndex={3}
/>
<ComboBox
size="content"
scaled={false}
scaledOptions={true}
onSelect={onChangeHeightDimension}
options={dataDimensions}
selectedOption={heightDimension}
displaySelectedOption
directionY="bottom"
/>
</RowContainer>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("FrameId")} />
<TextInput
scale={true}
onChange={onChangeFrameId}
placeholder={t("EnterId")}
value={config.frameId}
tabIndex={4}
/>
</ControlsGroup>
</ControlsSection>
{/* <InterfaceElements>
<Label className="label">{t("InterfaceElements")}</Label>
<Checkbox
className="checkbox"
label={t("TabPlugins")}
onChange={() => {}}
isChecked={true}
/>
<RowContainer>
<Checkbox label={t("Chat")} onChange={() => {}} isChecked={true} />
<Text color="gray">({t("InLeftPanel")})</Text>
</RowContainer>
<RowContainer>
<Checkbox label={t("FeedbackAndSupport")} onChange={() => {}} isChecked={true} />
<Text color="gray">({t("InLeftPanel")})</Text>
</RowContainer>
</InterfaceElements>
<CategorySubHeader>{t("AddWatermarks")}</CategorySubHeader>
<ControlsGroup>
<LabelGroup>
<Label className="label" text={t("SelectImage")} />
</LabelGroup>
<FilesSelectorInputWrapper>
<FilesSelectorInput onSelectFolder={onChangeFileId} isSelect />
</FilesSelectorInputWrapper>
</ControlsGroup>
<Label className="label" text={t("Scale")} />
<ComboBox
onSelect={() => {}}
options={[
{ key: "1", label: "100%", default: true },
{ key: "2", label: "50%" },
{ key: "3", label: "25%" },
]}
scaled={true}
selectedOption={{ key: "1", label: "100%", default: true }}
displaySelectedOption
directionY="top"
/>
<Label className="label" text={t("Rotate")} />
<ComboBox
onSelect={() => {}}
options={[
{ key: "1", label: "45%", default: true },
{ key: "2", label: "75%" },
{ key: "3", label: "90%" },
{ key: "4", label: "180%" },
]}
scaled={true}
selectedOption={{ key: "1", label: "45%", default: true }}
displaySelectedOption
directionY="top"
/>
<Label className="label" text={t("CreateEditRoomDialog:Icon")} />
<ImageEditor
t={t}
className="wrapper-image-editor"
classNameWrapperImageCropper="avatar-editor"
image={{}}
setPreview={() => {}}
onChangeImage={() => {}}
/> */}
</Controls>
</Container>
{!showPreview && (
<>
<GetCodeButtonWrapper>
<Button
id="get-sdk-code-button"
primary
size="normal"
scale
label={t("GetCode")}
onClick={openGetCodeModal}
/>
</GetCodeButtonWrapper>
<GetCodeDialog
t={t}
visible={isGetCodeDialogOpened}
codeBlock={codeBlock}
onClose={closeGetCodeModal}
/>
</>
)}
</SDKContainer>
);
};
export default inject(({ authStore, settingsStore }) => {
const { setDocumentTitle } = authStore;
const { theme } = settingsStore;
return {
theme,
setDocumentTitle,
};
})(
withTranslation(["JavascriptSdk", "Files", "EmbeddingPanel", "Common", "CreateEditRoomDialog"])(
observer(Viewer),
),
);

View File

@ -0,0 +1,39 @@
import React from "react";
import styled from "styled-components";
import { RectangleSkeleton } from "@docspace/shared/skeletons/rectangle";
import { Base } from "@docspace/shared/themes";
const StyledContainer = styled.div`
width: ${(props) => props.width};
height: ${(props) => props.height};
display: flex;
justify-content: center;
align-items: center;
border: 1px solid ${(props) => props.theme.plugins.borderColor};
border-radius: 6px;
overflow: hidden;
.emptyIframeText {
position: absolute;
font-size: 44px;
font-weight: 700;
line-height: 59.92px;
color: ${(props) => props.theme.text.emailColor};
}
`;
StyledContainer.defaultProps = { theme: Base };
const EmptyIframeContainer = ({ text, width, height }) => {
return (
<StyledContainer width={width} height={height}>
<RectangleSkeleton width="100%" height="100%" borderRadius="6px" />
<span className="emptyIframeText">{text}</span>
</StyledContainer>
);
};
export default EmptyIframeContainer;

View File

@ -0,0 +1,82 @@
import React from "react";
import styled, { css } from "styled-components";
import { Base } from "@docspace/shared/themes";
import { Text } from "@docspace/shared/components/text";
import { Button } from "@docspace/shared/components/button";
import ArrowIcon from "PUBLIC_DIR/images/arrow-left.react.svg";
const TileContainer = styled.div`
box-sizing: border-box;
width: 100%;
max-width: 342px;
height: 354px;
padding: 12px 16px;
border-radius: 6px;
border: 1px solid ${(props) => props.theme.sdkPresets.borderColor};
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 12px;
.tileContent {
display: flex;
flex-direction: column;
gap: 12px;
}
.navigationButton {
border: none;
.button-content {
flex-direction: row-reverse;
}
.icon {
transform: scale(-1, 1);
}
:hover {
${() =>
css`
border: ${(props) => props.theme.button.border.baseHover};
box-sizing: ${(props) => props.theme.button.boxSizing};
`}
}
}
`;
TileContainer.defaultProps = { theme: Base };
const PresetTile = (props) => {
const { t, title, description, image, handleOnClick } = props;
return (
<TileContainer>
<div className="tileContent">
<Text fontSize="16px" lineHeight="22px" fontWeight={700}>
{title}
</Text>
<img src={image} alt={title} />
<Text lineHeight="20px">{description}</Text>
</div>
<Button
className="navigationButton"
label={t("SetUp")}
icon={<ArrowIcon />}
scale
isClicked
size="small"
onClick={handleOnClick}
/>
</TileContainer>
);
};
export default PresetTile;

View File

@ -0,0 +1,49 @@
import React from "react";
import { Text } from "@docspace/shared/components/text";
import styled from "styled-components";
import XImg from "PUBLIC_DIR/images/x.react.svg";
const Wrapper = styled.div`
box-sizing: border-box;
max-width: 216px;
width: 100%;
padding: 8px 4px 16px;
`;
const HeaderContainer = styled.header`
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 8px;
svg {
cursor: pointer;
path {
fill: #333;
}
}
`;
const ImgWrapper = styled.div`
margin-top: 16px;
`;
export const TooltipContent = ({ title, description, img }) => {
return (
<Wrapper>
<HeaderContainer>
<Text fontSize="16px" fontWeight={700} lineHeight="22px">
{title}
</Text>
{/* <XImg /> */}
</HeaderContainer>
<Text>{description}</Text>
<ImgWrapper>
<img src={img} alt={title} />
</ImgWrapper>
</Wrapper>
);
};

View File

@ -8,6 +8,7 @@ import { Text } from "@docspace/shared/components/text";
import { SelectorAddButton } from "@docspace/shared/components/selector-add-button";
import { SelectedItem } from "@docspace/shared/components/selected-item";
import { tablet } from "@docspace/shared/utils";
import Base from "@docspace/shared/themes/base";
const CategoryHeader = styled.div`
margin-top: 24px;
@ -23,6 +24,7 @@ const Container = styled.div`
&.description-holder {
display: block;
color: ${(props) => props.theme.sdkPresets.secondaryColor};
}
&.description-holder > div {
@ -41,6 +43,8 @@ const Container = styled.div`
}
`;
Container.defaultProps = { theme: Base };
const ChipsContainer = styled.div`
display: flex;
align-items: center;

View File

@ -129,7 +129,9 @@ const DeliveryDatePicker = ({
const SelectedDateTime = () => {
const formattedTime = isTimeEqual
? ""
: ` ${filters.deliveryFrom.format("HH:mm")} - ${moment(filters.deliveryTo)
: ` ${moment(filters.deliveryFrom).tz(window.timezone).format("HH:mm")} - ${moment(
filters.deliveryTo,
)
.tz(window.timezone)
.format("HH:mm")}`;
@ -210,6 +212,7 @@ const DeliveryDatePicker = ({
hasError={!isTimeValid}
tabIndex={1}
locale={i18n.language}
initialTime={filters.deliveryFrom}
/>
</span>
@ -219,10 +222,11 @@ const DeliveryDatePicker = ({
<TimePicker
classNameInput="before-time"
date={filters.deliveryTo}
setDate={setDeliveryTo}
onChange={setDeliveryTo}
hasError={!isTimeValid}
tabIndex={2}
locale={i18n.language}
initialTime={filters.deliveryTo}
/>
</TimePickerCell>
) : (

View File

@ -37,6 +37,7 @@ const StatusBadgeSelector = ({
label={label}
onClick={handleOnClick}
primary={isStatusSelected(statusCode)}
size="extraSmall"
/>
);
};
@ -77,7 +78,7 @@ const StatusPicker = ({ filters, setFilters }) => {
handleStatusClick={handleStatusClick}
key={code}
/>
)
),
);
return (

View File

@ -178,6 +178,7 @@ const FilterDialog = (props) => {
size="normal"
primary={true}
onClick={handleApplyFilters}
isDisabled={filters.deliveryTo <= filters.deliveryFrom}
/>
<Button
className="cancel-button"

View File

@ -43,16 +43,14 @@ const StatusBar = (props) => {
<SelectedItem
label={
moment(historyFilters.deliveryDate)
.format("DD MMM YYYY")
.tz(window.timezone) +
.tz(window.timezone)
.format("DD MMM YYYY") +
" " +
moment(historyFilters.deliveryFrom)
.format("HH:mm")
.tz(window.timezone) +
" - " +
moment(historyFilters.deliveryTo)
.format("HH:mm")
.tz(window.timezone)
.format("HH:mm") +
" - " +
moment(historyFilters.deliveryTo).tz(window.timezone).format("HH:mm")
}
onClose={clearDate}
onClick={clearDate}

View File

@ -51,6 +51,7 @@ const StatusBadge = (props) => {
maxWidth="80px"
fontWeight={700}
noHover
isVersionBadge
/>
);
};

View File

@ -15,7 +15,6 @@ import {
sloBindingOptions,
nameIdOptions,
} from "./sub-components/constants";
import { useIsMobileView } from "../../../utils/useIsMobileView";
import { DeviceType } from "@docspace/shared/enums";
const PROVIDER_URL = "https://idpservice/idp";

View File

@ -8,9 +8,9 @@ import { Text } from "@docspace/shared/components/text";
import { Button } from "@docspace/shared/components/button";
import { mobile, size } from "@docspace/shared/utils";
import { DeviceType } from "@docspace/shared/enums";
import MetadataUrlField from "./sub-components/MetadataUrlField";
import { useIsMobileView } from "../../../utils/useIsMobileView";
const StyledWrapper = styled.div`
.button-wrapper {
@ -30,11 +30,11 @@ const StyledWrapper = styled.div`
const ProviderMetadata = (props) => {
const { t } = useTranslation("SingleSignOn");
const isMobileView = useIsMobileView();
const navigate = useNavigate();
const location = useLocation();
const { downloadMetadata, currentDeviceType } = props;
const { downloadMetadata } = props;
const isMobileView = currentDeviceType === DeviceType.mobile;
const url = window.location.origin;
@ -95,10 +95,12 @@ const ProviderMetadata = (props) => {
);
};
export default inject(({ ssoStore }) => {
export default inject(({ ssoStore, settingsStore }) => {
const { downloadMetadata } = ssoStore;
const { currentDeviceType } = settingsStore;
return {
downloadMetadata,
currentDeviceType
};
})(observer(ProviderMetadata));

View File

@ -18,6 +18,7 @@ const SubmitResetButtons = (props) => {
hasErrors,
hasChanges,
isLoadingXml,
enableSso,
} = props;
return (
@ -32,7 +33,9 @@ const SubmitResetButtons = (props) => {
displaySettings={true}
hasScroll={true}
isSaving={isSubmitLoading}
saveButtonDisabled={hasErrors || !hasChanges || isLoadingXml}
saveButtonDisabled={
!enableSso || hasErrors || !hasChanges || isLoadingXml
}
cancelEnable={!(isSubmitLoading || isLoadingXml)}
additionalClassSaveButton="save-button"
additionalClassCancelButton="restore-button"
@ -53,6 +56,7 @@ export default inject(({ ssoStore }) => {
hasErrors,
hasChanges,
isLoadingXml,
enableSso,
} = ssoStore;
return {
@ -65,5 +69,6 @@ export default inject(({ ssoStore }) => {
hasErrors,
hasChanges,
isLoadingXml,
enableSso,
};
})(observer(SubmitResetButtons));

View File

@ -13,7 +13,6 @@ import ToggleSSO from "./sub-components/ToggleSSO";
import SSOLoader from "./sub-components/ssoLoader";
import MobileView from "./MobileView";
import { useIsMobileView } from "../../../utils/useIsMobileView";
import { DeviceType } from "@docspace/shared/enums";
const SERVICE_PROVIDER_SETTINGS = "serviceProviderSettings";

View File

@ -1,66 +0,0 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { useTranslation } from "react-i18next";
import { Box } from "@docspace/shared/components/box";
import { Button } from "@docspace/shared/components/button";
import { ModalDialog } from "@docspace/shared/components/modal-dialog";
import { Text } from "@docspace/shared/components/text";
import StyledModalDialog from "../styled-containers/StyledModalDialog";
const DisableSsoConfirmationModal = (props) => {
const { t } = useTranslation(["SingleSignOn", "Common"]);
const {
closeConfirmationDisableModal,
confirmationDisableModal,
confirmDisable,
} = props;
return (
<StyledModalDialog
contentHeight="100%"
displayType="modal"
onClose={closeConfirmationDisableModal}
visible={confirmationDisableModal}
>
<ModalDialog.Header>{t("Common:Confirmation")}</ModalDialog.Header>
<ModalDialog.Body>
<Text noSelect>{t("ConfirmationText")}</Text>
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
id="ok-button"
label={t("Common:OKButton")}
onClick={confirmDisable}
primary
scale
size="normal"
/>
<Button
id="cancel-button"
label={t("Common:CancelButton")}
onClick={closeConfirmationDisableModal}
scale
size="normal"
/>
</ModalDialog.Footer>
</StyledModalDialog>
);
};
export default inject(({ ssoStore }) => {
const {
closeConfirmationDisableModal,
confirmationDisableModal,
confirmDisable,
} = ssoStore;
return {
closeConfirmationDisableModal,
confirmationDisableModal,
confirmDisable,
};
})(observer(DisableSsoConfirmationModal));

View File

@ -6,21 +6,11 @@ import { Box } from "@docspace/shared/components/box";
import { Text } from "@docspace/shared/components/text";
import { ToggleButton } from "@docspace/shared/components/toggle-button";
import { Badge } from "@docspace/shared/components/badge";
import DisableSsoConfirmationModal from "./DisableSsoConfirmationModal";
const borderProp = { radius: "6px" };
const ToggleSSO = (props) => {
const {
theme,
enableSso,
isSsoEnabled,
openConfirmationDisableModal,
ssoToggle,
confirmationDisableModal,
isSSOAvailable,
t,
} = props;
const { theme, enableSso, ssoToggle, isSSOAvailable, t } = props;
return (
<>
@ -40,9 +30,7 @@ const ToggleSSO = (props) => {
<ToggleButton
className="enable-sso toggle"
isChecked={enableSso}
onChange={
isSsoEnabled && enableSso ? openConfirmationDisableModal : ssoToggle
}
onChange={() => ssoToggle(t)}
isDisabled={!isSSOAvailable}
/>
@ -77,28 +65,17 @@ const ToggleSSO = (props) => {
</Text>
</div>
</Box>
{confirmationDisableModal && <DisableSsoConfirmationModal />}
</>
);
};
export default inject(({ settingsStore, ssoStore }) => {
const { theme } = settingsStore;
const {
enableSso,
isSsoEnabled,
openConfirmationDisableModal,
ssoToggle,
confirmationDisableModal,
} = ssoStore;
const { enableSso, ssoToggle } = ssoStore;
return {
theme,
enableSso,
isSsoEnabled,
openConfirmationDisableModal,
ssoToggle,
confirmationDisableModal,
};
})(withTranslation(["SingleSignOn"])(observer(ToggleSSO)));

View File

@ -334,6 +334,50 @@ export const settingsTree = [
link: "javascript-sdk",
tKey: "DeveloperTools",
isCategory: true,
children: [
{
id: "portal-settings_catalog-javascript-sdk_simple-room",
key: "5-1-0",
icon: "",
link: "room",
tKey: "Common:Room",
},
{
id: "portal-settings_catalog-javascript-sdk_manager",
key: "5-1-1",
icon: "",
link: "manager",
tKey: "JavascriptSdk:Manager",
},
{
id: "portal-settings_catalog-javascript-sdk_room-selector",
key: "5-1-2",
icon: "",
link: "room-selector",
tKey: "JavascriptSdk:RoomSelector",
},
{
id: "portal-settings_catalog-javascript-sdk_file-selector",
key: "5-1-3",
icon: "",
link: "file-selector",
tKey: "JavascriptSdk:FileSelector",
},
{
id: "portal-settings_catalog-javascript-sdk_editor",
key: "5-1-4",
icon: "",
link: "editor",
tKey: "JavascriptSdk:Editor",
},
{
id: "portal-settings_catalog-javascript-sdk_viewer",
key: "5-1-5",
icon: "",
link: "viewer",
tKey: "JavascriptSdk:Viewer",
},
],
},
{
id: "portal-settings_catalog-plugin-sdk",

View File

@ -1,22 +0,0 @@
import { useState, useLayoutEffect } from "react";
import { size } from "@docspace/shared/utils";
export const useIsMobileView = () => {
const [isMobileView, setIsMobileView] = useState(false);
const onCheckView = () => {
window.innerWidth <= size.mobile
? setIsMobileView(true)
: setIsMobileView(false);
};
useLayoutEffect(() => {
onCheckView();
window.addEventListener("resize", onCheckView);
return () => window.removeEventListener("resize", onCheckView);
}, []);
return isMobileView;
};

View File

@ -145,6 +145,13 @@ const PreparationPortal = (props) => {
returnToPortal();
}
} catch (error) {
const status = err?.response?.status;
const needCreationTableTime = status === 404;
if (needCreationTableTime) {
return;
}
clearAllIntervals();
setErrorMessage(error);
}
@ -249,7 +256,7 @@ const PreparationPortalWrapper = inject(({ backup }) => {
multiplicationFactor,
};
})(
withTranslation(["PreparationPortal", "Common"])(observer(PreparationPortal))
withTranslation(["PreparationPortal", "Common"])(observer(PreparationPortal)),
);
PreparationPortal.propTypes = {

View File

@ -1,4 +1,5 @@
import { useEffect, useState, useCallback } from "react";
import { useState, useEffect, useCallback, useRef } from "react";
import { withTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import { useParams } from "react-router-dom";
import AppLoader from "@docspace/common/components/AppLoader";
@ -13,6 +14,7 @@ import {
import { RoomsType } from "@docspace/shared/enums";
const Sdk = ({
t,
frameConfig,
match,
setFrameConfig,
@ -30,6 +32,14 @@ const Sdk = ({
}) => {
const [isDataReady, setIsDataReady] = useState(false);
const formatsDescription = {
DOCX: t("Common:SelectDOCXFormat"),
DOCXF: t("Common:SelectDOCXFFormat"),
BackupOnly: t("Common:SelectBackupOnlyFormat"),
IMG: t("Common:SelectIMGFormat"),
XLSX: t("Common:SelectXLSXFormat"),
};
useEffect(() => {
window.addEventListener("message", handleMessage, false);
return () => {
@ -134,8 +144,8 @@ const Sdk = ({
const onSelectRoom = useCallback(
async (data) => {
if (data[0].logo.large !== "") {
data[0].icon = toRelativeUrl(data[0].logo.large);
if (data[0].logo?.large !== "") {
data[0].icon = toRelativeUrl(data[0].logo?.large);
} else {
data[0].icon = await getRoomsIcon(data[0].roomType, false, 32);
}
@ -193,17 +203,30 @@ const Sdk = ({
: {};
let component;
switch (mode) {
case "room-selector":
const cancelButtonProps = frameConfig?.showSelectorCancel
? { withCancelButton: true, cancelButtonLabel: "", onCancel: onClose }
? {
withCancelButton: true,
cancelButtonLabel: frameConfig?.cancelButtonLabel,
onCancel: onClose,
}
: {};
const headerProps = frameConfig?.showSelectorHeader
? { withHeader: true, headerProps: { headerLabel: "" } }
: {};
component = (
<RoomSelector
{...cancelButtonProps}
withHeader={frameConfig?.showSelectorHeader}
{...headerProps}
onSubmit={onSelectRoom}
withSearch={frameConfig?.withSearch}
submitButtonLabel={frameConfig?.acceptButtonLabel}
roomType={frameConfig?.roomType}
onSelect={() => {}}
setIsDataReady={setIsDataReady}
isMultiSelect={false}
/>
@ -219,10 +242,19 @@ const Sdk = ({
setIsDataReady={setIsDataReady}
onSelectFile={onSelectFile}
onClose={onClose}
filterParam={"ALL"}
withBreadCrumbs={frameConfig?.withBreadCrumbs}
withSubtitle={frameConfig?.withSubtitle}
filterParam={frameConfig?.filterParam}
isUserOnly={selectorType === "userFolderOnly"}
isRoomsOnly={selectorType === "roomsOnly"}
withCancelButton={frameConfig?.showSelectorCancel}
withSearch={frameConfig?.withSearch}
acceptButtonLabel={frameConfig?.acceptButtonLabel}
cancelButtonLabel={frameConfig?.cancelButtonLabel}
currentFolderId={frameConfig?.id}
descriptionText={
formatsDescription[frameConfig?.filterParam || "DOCX"]
}
/>
);
break;
@ -268,4 +300,4 @@ export default inject(
getFilePrimaryLink,
};
},
)(observer(Sdk));
)(withTranslation(["JavascriptSdk", "Common"])(observer(Sdk)));

View File

@ -135,6 +135,25 @@ const RestoreBackup = loadable(() =>
);
const Bonus = loadable(() => import("../pages/Bonus"));
const SimpleRoom = loadable(() =>
import("../pages/PortalSettings/categories/developer-tools/JavascriptSDK/presets/SimpleRoom")
);
const Manager = loadable(() =>
import("../pages/PortalSettings/categories/developer-tools/JavascriptSDK/presets/Manager")
);
const RoomSelector = loadable(() =>
import("../pages/PortalSettings/categories/developer-tools/JavascriptSDK/presets/RoomSelector")
);
const FileSelector = loadable(() =>
import("../pages/PortalSettings/categories/developer-tools/JavascriptSDK/presets/FileSelector")
);
const Editor = loadable(() =>
import("../pages/PortalSettings/categories/developer-tools/JavascriptSDK/presets/Editor")
);
const Viewer = loadable(() =>
import("../pages/PortalSettings/categories/developer-tools/JavascriptSDK/presets/Viewer")
);
const PortalSettingsRoutes = {
path: "portal-settings/",
element: (
@ -290,6 +309,30 @@ const PortalSettingsRoutes = {
path: "developer-tools/javascript-sdk",
element: <DeveloperTools />,
},
{
path: "developer-tools/javascript-sdk/room",
element: <SimpleRoom />,
},
{
path: "developer-tools/javascript-sdk/manager",
element: <Manager />,
},
{
path: "developer-tools/javascript-sdk/room-selector",
element: <RoomSelector />,
},
{
path: "developer-tools/javascript-sdk/file-selector",
element: <FileSelector />,
},
{
path: "developer-tools/javascript-sdk/editor",
element: <Editor />,
},
{
path: "developer-tools/javascript-sdk/viewer",
element: <Viewer />,
},
{
path: "developer-tools/plugin-sdk",
element: <DeveloperTools />,

View File

@ -98,7 +98,6 @@ class SsoFormStore {
spMetadata = false;
idpIsModalVisible = false;
spIsModalVisible = false;
confirmationDisableModal = false;
confirmationResetModal = false;
// errors
@ -155,12 +154,13 @@ class SsoFormStore {
}
};
ssoToggle = () => {
ssoToggle = (t) => {
if (!this.enableSso) {
this.enableSso = true;
this.serviceProviderSettings = true;
} else {
this.enableSso = false;
!this.hasErrors && this.saveSsoSettings(t);
}
for (let key in this) {
@ -224,14 +224,6 @@ class SsoFormStore {
this.defaultSettings = defaultSettings;
};
openConfirmationDisableModal = () => {
this.confirmationDisableModal = true;
};
closeConfirmationDisableModal = () => {
this.confirmationDisableModal = false;
};
openResetModal = () => {
this.confirmationResetModal = true;
};
@ -240,13 +232,6 @@ class SsoFormStore {
this.confirmationResetModal = false;
};
confirmDisable = () => {
this.resetForm();
this.setIsSsoEnabled(false);
this.ssoToggle();
this.confirmationDisableModal = false;
};
confirmReset = () => {
this.resetForm();
this.setIsSsoEnabled(false);
@ -552,12 +537,12 @@ class SsoFormStore {
if (meta.singleSignOnService) {
this.ssoUrlPost = this.getPropValue(
meta.singleSignOnService,
BINDING_POST
BINDING_POST,
);
this.ssoUrlRedirect = this.getPropValue(
meta.singleSignOnService,
BINDING_REDIRECT
BINDING_REDIRECT,
);
}
@ -568,12 +553,12 @@ class SsoFormStore {
this.sloUrlRedirect = this.getPropValue(
meta.singleLogoutService,
BINDING_REDIRECT
BINDING_REDIRECT,
);
this.sloUrlPost = this.getPropValue(
meta.singleLogoutService,
BINDING_POST
BINDING_POST,
);
}
@ -598,7 +583,7 @@ class SsoFormStore {
if (meta.certificate.signing) {
if (Array.isArray(meta.certificate.signing)) {
meta.certificate.signing = this.getUniqueItems(
meta.certificate.signing
meta.certificate.signing,
).reverse();
meta.certificate.signing.forEach((signingCrt) => {
data.push({
@ -690,14 +675,14 @@ class SsoFormStore {
delSpCertificate = (action) => {
this.resetSpCheckboxes(action);
this.spCertificates = this.spCertificates.filter(
(certificate) => certificate.action !== action
(certificate) => certificate.action !== action,
);
};
delIdpCertificate = (cert) => {
this.resetIdpCheckboxes();
this.idpCertificates = this.idpCertificates.filter(
(certificate) => certificate.crt !== cert
(certificate) => certificate.crt !== cert,
);
};
@ -713,7 +698,7 @@ class SsoFormStore {
(item) =>
(item.action === this.spAction ||
item.action === SSO_SIGNING_ENCRYPT) &&
!this.isEdit
!this.isEdit,
);
};
@ -785,7 +770,7 @@ class SsoFormStore {
if (
this.idpCertificates.find(
(item) => item.crt === this.idpCertificate && !this.isEdit
(item) => item.crt === this.idpCertificate && !this.isEdit,
)
) {
toastr.error(t("CertificateExist"));
@ -896,7 +881,7 @@ class SsoFormStore {
if (!this.enableSso || this.isLoadingXml) return true;
return !this.spCertificates.some(
(cert) =>
cert.action === SSO_SIGNING || cert.action === SSO_SIGNING_ENCRYPT
cert.action === SSO_SIGNING || cert.action === SSO_SIGNING_ENCRYPT,
);
}
@ -904,7 +889,7 @@ class SsoFormStore {
if (!this.enableSso || this.isLoadingXml) return true;
return !this.spCertificates.some(
(cert) =>
cert.action === SSO_ENCRYPT || cert.action === SSO_SIGNING_ENCRYPT
cert.action === SSO_ENCRYPT || cert.action === SSO_SIGNING_ENCRYPT,
);
}
}

View File

@ -2,6 +2,7 @@ import { makeAutoObservable } from "mobx";
import { TableVersions } from "SRC_DIR/helpers/constants";
const TABLE_COLUMNS = `filesTableColumns_ver-${TableVersions.Files}`;
const TABLE_ACCOUNTS_COLUMNS = `peopleTableColumns_ver-${TableVersions.Accounts}`;
const TABLE_ROOMS_COLUMNS = `roomsTableColumns_ver-${TableVersions.Rooms}`;
const TABLE_TRASH_COLUMNS = `trashTableColumns_ver-${TableVersions.Trash}`;
const TABLE_SDK_COLUMNS = `filesSDKTableColumns_ver-${TableVersions.Files}`;
@ -43,7 +44,9 @@ class TableStore {
createdTrashColumnIsEnabled = false;
sizeTrashColumnIsEnabled = false;
typeTrashColumnIsEnabled = false;
typeAccountsColumnIsEnabled = true;
emailAccountsColumnIsEnabled = true;
constructor(authStore, treeFoldersStore, userStore, settingsStore) {
makeAutoObservable(this);
@ -107,12 +110,17 @@ class TableStore {
setSizeTrashColumn = (enable) => (this.sizeTrashColumnIsEnabled = enable);
setTypeTrashColumn = (enable) => (this.typeTrashColumnIsEnabled = enable);
setAccountsColumnType = (enable) =>
(this.typeAccountsColumnIsEnabled = enable);
setAccountsColumnEmail = (enable) =>
(this.emailAccountsColumnIsEnabled = enable);
setColumnsEnable = () => {
const storageColumns = localStorage.getItem(this.tableStorageName);
const splitColumns = storageColumns && storageColumns.split(",");
if (splitColumns) {
const { isRoomsFolder, isArchiveFolder, isTrashFolder } =
const { isRoomsFolder, isArchiveFolder, isTrashFolder, isAccounts } =
this.treeFoldersStore;
const isRooms = isRoomsFolder || isArchiveFolder;
@ -124,6 +132,12 @@ class TableStore {
return;
}
if (isAccounts) {
this.setAccountsColumnType(splitColumns.includes("Type"));
this.setAccountsColumnEmail(splitColumns.includes("Mail"));
return;
}
if (isTrashFolder) {
this.setRoomColumn(splitColumns.includes("Room"));
this.setAuthorTrashColumn(splitColumns.includes("AuthorTrash"));
@ -145,7 +159,7 @@ class TableStore {
};
setColumnEnable = (key) => {
const { isRoomsFolder, isArchiveFolder, isTrashFolder } =
const { isRoomsFolder, isArchiveFolder, isTrashFolder, isAccounts } =
this.treeFoldersStore;
const isRooms = isRoomsFolder || isArchiveFolder;
@ -186,7 +200,9 @@ class TableStore {
case "Type":
isRooms
? this.setRoomColumnType(!this.roomColumnTypeIsEnabled)
: this.setTypeColumn(!this.typeColumnIsEnabled);
: isAccounts
? this.setAccountsColumnType(!this.typeAccountsColumnIsEnabled)
: this.setTypeColumn(!this.typeColumnIsEnabled);
return;
case "TypeTrash":
this.setTypeTrashColumn(!this.typeTrashColumnIsEnabled);
@ -208,6 +224,10 @@ class TableStore {
this.setRoomColumnActivity(!this.roomColumnActivityIsEnabled);
return;
case "Mail":
this.setAccountsColumnEmail(!this.emailAccountsColumnIsEnabled);
return;
default:
return;
}
@ -235,7 +255,7 @@ class TableStore {
};
get tableStorageName() {
const { isRoomsFolder, isArchiveFolder, isTrashFolder } =
const { isRoomsFolder, isArchiveFolder, isTrashFolder, isAccounts } =
this.treeFoldersStore;
const isRooms = isRoomsFolder || isArchiveFolder;
const userId = this.userStore.user?.id;
@ -245,9 +265,11 @@ class TableStore {
return isRooms
? `${TABLE_ROOMS_COLUMNS}=${userId}`
: isTrashFolder
? `${TABLE_TRASH_COLUMNS}=${userId}`
: `${TABLE_COLUMNS}=${userId}`;
: isAccounts
? `${TABLE_ACCOUNTS_COLUMNS}=${userId}`
: isTrashFolder
? `${TABLE_TRASH_COLUMNS}=${userId}`
: `${TABLE_COLUMNS}=${userId}`;
}
get columnStorageName() {

View File

@ -215,6 +215,10 @@ class TreeFoldersStore {
return this.recycleBinFolder ? this.recycleBinFolder.id : null;
}
get isAccounts() {
return window.location.pathname.includes("accounts/filter");
}
get isPersonalRoom() {
return (
this.myFolder &&

View File

@ -1,6 +1,6 @@
{
"name": "@docspace/common",
"version": "2.0.2",
"version": "2.0.3",
"private": true,
"scripts": {
"build": "echo 'skip it'",

View File

@ -1,6 +1,6 @@
{
"name": "@docspace/editor",
"version": "2.0.2",
"version": "2.0.3",
"private": true,
"homepage": "/doceditor",
"scripts": {

View File

@ -2,7 +2,7 @@ import React, { useEffect } from "react";
import Editor from "./components/Editor.js";
import { useSSR } from "react-i18next";
import useMfScripts from "./helpers/useMfScripts";
import { isRetina } from "@docspace/shared/utils/common";
import { isRetina, frameCallCommand } from "@docspace/shared/utils/common";
import { combineUrl } from "@docspace/shared/utils/combineUrl";
import { getCookie, setCookie } from "@docspace/shared/utils/cookie";
@ -40,7 +40,8 @@ const App = ({
switch (rest?.config?.documentType) {
case "word":
icon = rest?.config?.document?.fileType === "pdf" ? PDFIcoUrl : TextIcoUrl;
icon =
rest?.config?.document?.fileType === "pdf" ? PDFIcoUrl : TextIcoUrl;
break;
case "slide":
icon = PresentationIcoUrl;
@ -80,6 +81,8 @@ const App = ({
(isLoadingDocumentError || isLoadedDocument)
)
tempElm.outerHTML = "";
if (isLoadingDocumentError) frameCallCommand("setIsLoaded");
}
if (isRetina() && getCookie("is_retina") == null) {

View File

@ -23,7 +23,11 @@ import {
import { EditorWrapper } from "../components/StyledEditor";
import { useTranslation } from "react-i18next";
import withDialogs from "../helpers/withDialogs";
import { assign, frameCallEvent, frameCallCommand } from "@docspace/shared/utils/common";
import {
assign,
frameCallEvent,
frameCallCommand,
} from "@docspace/shared/utils/common";
import { getEditorTheme } from "@docspace/shared/utils";
import { toastr } from "@docspace/shared/components/toast";
import { DocumentEditor } from "@onlyoffice/document-editor-react";
@ -40,6 +44,7 @@ const onSDKInfo = (event) => {
};
const onSDKWarning = (event) => {
frameCallCommand("setIsLoaded");
console.log(
"ONLYOFFICE Document Editor reports a warning: code " +
event.data.warningCode +
@ -49,6 +54,7 @@ const onSDKWarning = (event) => {
};
const onSDKError = (event) => {
frameCallCommand("setIsLoaded");
console.log(
"ONLYOFFICE Document Editor reports an error: code " +
event.data.errorCode +
@ -272,10 +278,10 @@ function Editor({
documentType === "word"
? "docx"
: documentType === "slide"
? "pptx"
: documentType === "cell"
? "xlsx"
: "docxf";
? "pptx"
: documentType === "cell"
? "xlsx"
: "docxf";
let fileName = t("Common:NewDocument");
@ -565,14 +571,14 @@ function Editor({
console.log("onDocumentReady", arguments, { docEditor });
documentIsReady = true;
frameCallCommand("setIsLoaded");
config?.errorMessage && docEditor?.showMessage(config.errorMessage);
if (isSharingAccess) {
loadUsersRightsList(docEditor);
}
frameCallCommand("setIsLoaded");
assign(window, ["ASC", "Files", "Editor", "docEditor"], docEditor); //Do not remove: it's for Back button on Mobile App
};

View File

@ -1,6 +1,6 @@
{
"name": "@docspace/login",
"version": "2.0.2",
"version": "2.0.3",
"private": true,
"homepage": "/login",
"scripts": {

View File

@ -3,6 +3,7 @@ import { commonInputStyles } from "../../utils";
import { Base } from "../../themes";
export const Wrapper = styled.div`
position: relative;
.hex-value {
${commonInputStyles}
box-sizing: border-box;
@ -28,8 +29,14 @@ export const InputWrapper = styled.div<{ scale?: boolean }>`
align-items: center;
width: ${(props) => (props.scale ? "100%" : "fit-content")};
input {
font-family: ${(props) => props.theme.fontFamily};
}
`;
InputWrapper.defaultProps = { theme: Base };
export const ColorBlock = styled.span<{ isDisabled?: boolean }>`
position: absolute;
right: 8px;

View File

@ -258,15 +258,19 @@ const StyledInfoPanelToggleColorThemeWrapper = styled(ColorTheme) <{
`;
StyledInfoPanelToggleColorThemeWrapper.defaultProps = { theme: Base };
const StyledControlButtonContainer = styled.div<{ isFrame?: boolean }>`
const StyledControlButtonContainer = styled.div<{
isFrame?: boolean;
showTitle?: boolean;
}>`
${(props) =>
props.theme.interfaceDirection === "rtl"
props.showTitle &&
(props.theme.interfaceDirection === "rtl"
? css`
margin-right: 16px;
`
: css`
margin-left: 16px;
`}
`)}
display: flex;
align-items: center;
gap: 16px;

View File

@ -51,6 +51,7 @@ const Navigation = ({
titleIcon,
currentDeviceType,
rootRoomTitle,
showTitle,
navigationButtonLabel,
onNavigationButtonClick,
tariffBar,
@ -167,6 +168,7 @@ const Navigation = ({
isRootFolderTitle
onClick={onTextClick}
/>
{navigationTitleNode}
</div>
) : (
@ -234,7 +236,7 @@ const Navigation = ({
onBackToParentFolder={onBackToParentFolder}
/>
{navigationTitleContainerNode}
{showTitle && navigationTitleContainerNode}
<ControlButtons
isRootFolder={isRootFolder}
@ -251,6 +253,7 @@ const Navigation = ({
isFrame={isFrame}
isPublicRoom={isPublicRoom}
isTrashFolder={isTrashFolder}
showTitle={showTitle}
navigationButtonLabel={navigationButtonLabel}
onNavigationButtonClick={onNavigationButtonClick}
tariffBar={tariffBar}

View File

@ -60,6 +60,7 @@ export interface IControlButtonProps {
isPublicRoom?: boolean;
isTrashFolder?: boolean;
isMobile?: boolean;
showTitle?: boolean;
navigationButtonLabel?: string;
onNavigationButtonClick?: () => void;
tariffBar?: React.ReactNode;
@ -165,6 +166,7 @@ export interface INavigationProps {
titleIcon: string;
currentDeviceType: DeviceType;
rootRoomTitle: string;
showTitle: boolean;
navigationButtonLabel?: string;
onNavigationButtonClick?: () => void;
tariffBar: React.ReactNode;

View File

@ -30,6 +30,7 @@ const ControlButtons = ({
isPublicRoom,
isTrashFolder,
isMobile,
showTitle,
navigationButtonLabel,
onNavigationButtonClick,
tariffBar,
@ -53,7 +54,7 @@ const ControlButtons = ({
const isTabletView = isTablet();
return (
<StyledControlButtonContainer isFrame={isFrame}>
<StyledControlButtonContainer isFrame={isFrame} showTitle={showTitle}>
{!isRootFolder || (isTrashFolder && !isEmptyFilesList) ? (
<>
{!isMobile && canCreate && (

View File

@ -169,7 +169,7 @@ const TabsContainer = ({
))}
</NavItem>
</StyledScrollbar>
<div>{elements[state.activeTab].content}</div>
<div className="tabs_body">{elements[state.activeTab].content}</div>
</>
);
};

View File

@ -1,6 +1,6 @@
{
"name": "@docspace/shared",
"version": "2.0.2",
"version": "2.0.3",
"private": true,
"scripts": {
"build": "echo 'skip it'",

View File

@ -102,6 +102,10 @@ export interface FilesSelectorProps {
socketSubscribers: Set<string>;
disabledItems: string[] | number[];
filterParam?: string;
withoutBackButton: boolean;
withBreadCrumbs: boolean;
withSearch: boolean;
cancelButtonLabel: string;
getIcon?: (size: number, fileExst: string) => string;
treeFolders?: TFolder[];
onSetBaseFolderPath?: (

View File

@ -3,7 +3,7 @@ import {
TSelectorHeader,
TSelectorItem,
} from "../../components/selector/Selector.types";
import { TLogo } from "../../api/rooms/types";
import { RoomsType } from "../../enums";
export type RoomSelectorProps = TSelectorHeader &
@ -15,16 +15,9 @@ export type RoomSelectorProps = TSelectorHeader &
isMultiSelect: boolean;
onSubmit: (items: TSelectorItem[]) => void;
roomType?: RoomsType;
excludeItems?: number[];
setIsDataReady?: (value: boolean) => void;
submitButtonLabel?: string;
withSearch?: boolean;
};
export type TItem = {
id: number;
label: string;
icon: string;
color: string | undefined;
logo: TLogo;
roomType: RoomsType;
};

View File

@ -25,6 +25,13 @@ const RoomSelector = ({
className,
style,
excludeItems,
withSearch,
isMultiSelect,
submitButtonLabel,
onSubmit,
withHeader,
@ -33,9 +40,10 @@ const RoomSelector = ({
setIsDataReady,
withCancelButton,
isMultiSelect,
cancelButtonLabel,
onCancel,
roomType,
}: RoomSelectorProps) => {
const { t }: { t: TTranslation } = useTranslation(["RoomSelector", "Common"]);
@ -84,7 +92,7 @@ const RoomSelector = ({
filter.page = page;
filter.pageCount = PAGE_COUNT;
filter.type = roomType;
filter.filterValue = searchValue || null;
const {
@ -93,7 +101,9 @@ const RoomSelector = ({
count,
} = await api.rooms.getRooms(filter);
const rooms = convertToItems(folders);
const rooms = convertToItems(folders).filter((x) =>
excludeItems ? !excludeItems.includes(x.id) : true,
);
setHasNextPage(count === PAGE_COUNT);
@ -113,7 +123,7 @@ const RoomSelector = ({
setIsNextPageLoading(false);
},
[searchValue],
[excludeItems, roomType, searchValue],
);
const headerSelectorProps: TSelectorHeader = withHeader
@ -124,25 +134,27 @@ const RoomSelector = ({
headerLabel: headerProps.headerLabel || t("RoomList"),
},
}
: ({} as TSelectorHeader);
: {};
const cancelButtonSelectorProps: TSelectorCancelButton = withCancelButton
? {
withCancelButton,
cancelButtonLabel: t("Common:CancelButton"),
withCancelButton: true,
cancelButtonLabel: cancelButtonLabel || t("Common:CancelButton"),
onCancel,
}
: ({} as TSelectorCancelButton);
: {};
const searchSelectorProps: TSelectorSearch = {
withSearch: true,
searchPlaceholder: t("Common:Search"),
searchValue,
onSearch: onSearchAction,
onClearSearch: onClearSearchAction,
searchLoader: <SearchLoader />,
isSearchLoading: isFirstLoad.current,
};
const searchSelectorProps: TSelectorSearch = withSearch
? {
withSearch: true,
searchPlaceholder: t("Common:Search"),
searchValue,
onSearch: onSearchAction,
onClearSearch: onClearSearchAction,
searchLoader: <SearchLoader />,
isSearchLoading: isFirstLoad.current,
}
: {};
return (
<Selector
@ -154,7 +166,7 @@ const RoomSelector = ({
{...searchSelectorProps}
onSelect={(item) => setSelectedItem(item)}
items={items}
submitButtonLabel={t("Common:SelectAction")}
submitButtonLabel={submitButtonLabel || t("Common:SelectAction")}
onSubmit={onSubmit}
isMultiSelect={isMultiSelect}
emptyScreenImage={EmptyScreenCorporateSvgUrl}

View File

@ -13,7 +13,7 @@ import { getCookie, setCookie } from "../utils/cookie";
import { TTenantExtraRes } from "../api/portal/types";
import { TenantStatus } from "../enums";
import { COOKIE_EXPIRATION_YEAR, LANGUAGE } from "../constants";
import { TI18n } from "../types";
import { Nullable, TI18n } from "../types";
import { UserStore } from "./UserStore";
import { CurrentTariffStatusStore } from "./CurrentTariffStatusStore";
@ -36,7 +36,7 @@ class AuthStore {
providers: TThirdPartyProvider[] = [];
capabilities: TCapabilities = {} as TCapabilities;
capabilities: Nullable<TCapabilities> = null;
isInit = false;
@ -44,7 +44,7 @@ class AuthStore {
isUpdatingTariff = false;
tenantExtra: TTenantExtraRes = {} as TTenantExtraRes;
tenantExtra: Nullable<TTenantExtraRes> = null;
skipRequest = false;
@ -159,12 +159,14 @@ class AuthStore {
};
get isEnterprise() {
this.currentTariffStatusStore?.setIsEnterprise(this.tenantExtra.enterprise);
return this.tenantExtra.enterprise;
this.currentTariffStatusStore?.setIsEnterprise(
this.tenantExtra?.enterprise || false,
);
return this.tenantExtra?.enterprise;
}
get isCommunity() {
return this.tenantExtra.opensource;
return this.tenantExtra?.opensource;
}
getTenantExtra = async () => {

View File

@ -15,9 +15,10 @@ import {
COUNT_FOR_SHOWING_BAR,
PERCENTAGE_FOR_SHOWING_BAR,
} from "../constants";
import { Nullable } from "../types";
class CurrentQuotasStore {
currentPortalQuota: TPaymentQuota = {} as TPaymentQuota;
currentPortalQuota: Nullable<TPaymentQuota> = null;
currentPortalQuotaFeatures: TPaymentFeature[] = [];
@ -32,15 +33,15 @@ class CurrentQuotasStore {
};
get isFreeTariff() {
return this.currentPortalQuota.free;
return this.currentPortalQuota?.free;
}
get isTrial() {
return this.currentPortalQuota.trial;
return this.currentPortalQuota?.trial;
}
get currentPlanCost() {
if (this.currentPortalQuota.price) return this.currentPortalQuota.price;
if (this.currentPortalQuota?.price) return this.currentPortalQuota.price;
return { value: 0, currencySymbol: "" };
}
@ -159,7 +160,7 @@ class CurrentQuotasStore {
}
get currentTariffPlanTitle() {
return this.currentPortalQuota.title;
return this.currentPortalQuota?.title;
}
get quotaCharacteristics() {

View File

@ -9,9 +9,10 @@ import { TPortalTariff } from "../api/portal/types";
import { TUser } from "../api/people/types";
import { isValidDate } from "../utils";
import { getDaysLeft, getDaysRemaining } from "../utils/common";
import { Nullable } from "../types";
class CurrentTariffStatusStore {
portalTariffStatus: TPortalTariff = {} as TPortalTariff;
portalTariffStatus: Nullable<TPortalTariff> = null;
isLoaded = false;
@ -38,35 +39,37 @@ class CurrentTariffStatusStore {
};
get isGracePeriod() {
return this.portalTariffStatus.state === TariffState.Delay;
return this.portalTariffStatus?.state === TariffState.Delay;
}
get isPaidPeriod() {
return this.portalTariffStatus.state === TariffState.Paid;
return this.portalTariffStatus?.state === TariffState.Paid;
}
get isNotPaidPeriod() {
return this.portalTariffStatus.state === TariffState.NotPaid;
return this.portalTariffStatus?.state === TariffState.NotPaid;
}
get dueDate() {
return this.portalTariffStatus.dueDate;
return this.portalTariffStatus ? this.portalTariffStatus.dueDate : null;
}
get delayDueDate() {
return this.portalTariffStatus.delayDueDate;
return this.portalTariffStatus
? this.portalTariffStatus.delayDueDate
: null;
}
get customerId() {
return this.portalTariffStatus.customerId;
return this.portalTariffStatus?.customerId;
}
get portalStatus() {
return this.portalTariffStatus.portalStatus;
return this.portalTariffStatus?.portalStatus;
}
get licenseDate() {
return this.portalTariffStatus.licenseDate;
return this.portalTariffStatus?.licenseDate;
}
setPayerInfo = async () => {
@ -97,7 +100,6 @@ class CurrentTariffStatusStore {
get isPaymentDateValid() {
if (this.dueDate === null) return false;
return isValidDate(this.dueDate);
}

View File

@ -2,9 +2,10 @@ import { makeAutoObservable, runInAction } from "mobx";
import api from "../api";
import { TPaymentFeature, TPaymentQuota } from "../api/portal/types";
import { MANAGER, TOTAL_SIZE } from "../constants";
import { Nullable } from "../types";
class PaymentQuotasStore {
portalPaymentQuotas: TPaymentQuota = {} as TPaymentQuota;
portalPaymentQuotas: Nullable<TPaymentQuota> = null;
portalPaymentQuotasFeatures: TPaymentFeature[] = [];
@ -19,7 +20,7 @@ class PaymentQuotasStore {
};
get planCost() {
if (this.portalPaymentQuotas.price) return this.portalPaymentQuotas.price;
if (this.portalPaymentQuotas?.price) return this.portalPaymentQuotas.price;
return { value: 0, currencySymbol: "" };
}
@ -56,7 +57,7 @@ class PaymentQuotasStore {
}
get tariffPlanTitle() {
return this.portalPaymentQuotas.title;
return this.portalPaymentQuotas?.title;
}
setPortalPaymentQuotas = async () => {

View File

@ -45,6 +45,7 @@ import { Dark, Base, TColorScheme } from "../themes";
import { toastr } from "../components/toast";
import { TData } from "../components/toast/Toast.type";
import { version } from "../package.json";
import { Nullable } from "../types";
// import { getFromLocalStorage } from "@docspace/client/src/pages/PortalSettings/utils";
@ -143,7 +144,7 @@ class SettingsStore {
uploadDashboard: "",
};
logoUrl: TWhiteLabel = {} as TWhiteLabel;
logoUrl: Nullable<TWhiteLabel> = null;
isDesktopClient = isDesktopEditors;
@ -172,7 +173,7 @@ class SettingsStore {
folderPath: TFolder[] = [];
hashSettings: TPasswordHash = {} as TPasswordHash;
hashSettings: Nullable<TPasswordHash> = null;
title = "";
@ -180,7 +181,7 @@ class SettingsStore {
nameSchemaId = null;
owner: TUser = {} as TUser;
owner: Nullable<TUser> = null;
wizardToken = null;
@ -234,7 +235,7 @@ class SettingsStore {
selectedThemeId: number | null = null;
currentColorScheme: TColorScheme = {} as TColorScheme;
currentColorScheme: Nullable<TColorScheme> = null;
enablePlugins = false;
@ -242,11 +243,11 @@ class SettingsStore {
domainValidator: TDomainValidator | null = null;
additionalResourcesData: TAdditionalResources = {} as TAdditionalResources;
additionalResourcesData: Nullable<TAdditionalResources> = null;
additionalResourcesIsDefault = true;
companyInfoSettingsData: TCompanyInfo = {} as TCompanyInfo;
companyInfoSettingsData: Nullable<TCompanyInfo> = null;
companyInfoSettingsIsDefault = true;
@ -499,7 +500,7 @@ class SettingsStore {
};
getSettings = async () => {
let newSettings: TSettings = {} as TSettings;
let newSettings: Nullable<TSettings> = null;
if (window?.__ASC_INITIAL_EDITOR_STATE__?.portalSettings)
newSettings = window.__ASC_INITIAL_EDITOR_STATE__.portalSettings;
@ -514,7 +515,7 @@ class SettingsStore {
Object.keys(newSettings).forEach((forEachKey) => {
const key = forEachKey as keyof TSettings;
if (key in this) {
if (key in this && newSettings) {
this.setValue(
key as keyof SettingsStore,
key === "defaultPage"
@ -530,7 +531,7 @@ class SettingsStore {
});
}
}
} else if (key === "passwordHash") {
} else if (key === "passwordHash" && newSettings) {
this.setValue("hashSettings", newSettings[key]);
}
});
@ -748,6 +749,7 @@ class SettingsStore {
getPortalOwner = async () => {
const owner = await api.people.getUserById(this.ownerId);
this.setPortalOwner(owner);
return owner;
};
@ -965,7 +967,7 @@ class SettingsStore {
this.frameConfig = frameConfig;
});
if (!!frameConfig) {
if (frameConfig) {
frameCallEvent({
event: "onAppReady",
data: { frameId: frameConfig.frameId },
@ -992,13 +994,13 @@ class SettingsStore {
};
getAppearanceTheme = async () => {
let res: TGetColorTheme = {} as TGetColorTheme;
let res: Nullable<TGetColorTheme> = null;
if (window?.__ASC_INITIAL_EDITOR_STATE__?.appearanceTheme)
res = window.__ASC_INITIAL_EDITOR_STATE__.appearanceTheme;
else res = await api.settings.getAppearanceTheme();
const currentColorScheme = res.themes.find((theme) => {
return res.selected === theme.id;
return res && res.selected === theme.id;
});
this.setAppearanceTheme(res.themes);
@ -1100,4 +1102,3 @@ class SettingsStore {
}
export { SettingsStore };

View File

@ -3209,6 +3209,11 @@ export const getBaseTheme = () => {
pluginName: "#5C5C5C",
descriptionColor: "#657077",
},
sdkPresets: {
borderColor: "#d0d5da",
secondaryColor: "#657077",
},
};
};

View File

@ -3184,6 +3184,11 @@ const Dark: TTheme = {
pluginName: "#A3A9AE",
descriptionColor: "#ADADAD",
},
sdkPresets: {
borderColor: "#474747",
secondaryColor: "#ADADAD",
},
};
export default Dark;

View File

@ -15,6 +15,8 @@ export type TViewAs = "tile" | "table" | "row" | "settings" | "profile";
export type TTranslation = (key: string, prop?: unknown) => string;
export type Nullable<T> = T | null;
export type NonFunctionPropertyNames<T, ExcludeTypes> = {
[K in keyof T]: T[K] extends ExcludeTypes ? never : K;
}[keyof T];

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.02215 15.2038L0.421696 12.1631C-0.140565 11.8977 -0.140565 11.4874 0.421696 11.2461L2.71963 10.1843L6.99771 12.1631C7.55997 12.4286 8.46448 12.4286 9.00229 12.1631L13.2804 10.1843L15.5783 11.2461C16.1406 11.5116 16.1406 11.9218 15.5783 12.1631L8.97785 15.2038C8.46448 15.4451 7.55997 15.4451 7.02215 15.2038Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.00076 11.441L0.420411 8.39689C-0.140137 8.13114 -0.140137 7.72042 0.420411 7.47882L2.6626 6.43994L7.00076 8.44521C7.56131 8.71097 8.46306 8.71097 8.99924 8.44521L13.3374 6.43994L15.5796 7.47882C16.1401 7.74458 16.1401 8.1553 15.5796 8.39689L8.99924 11.441C8.43869 11.7068 7.53694 11.7068 7.00076 11.441Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.00076 7.71748L0.420411 4.71714C-0.140137 4.45521 -0.140137 4.0504 0.420411 3.81227L7.00076 0.81193C7.56131 0.549995 8.46306 0.549995 8.99924 0.81193L15.5796 3.81227C16.1401 4.07421 16.1401 4.47902 15.5796 4.71714L8.99924 7.71748C8.43869 7.95561 7.53694 7.95561 7.00076 7.71748Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,9 @@
<svg width="310" height="180" viewBox="0 0 310 180" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="310" height="180" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_986_510" transform="scale(0.0016129 0.00277778)"/>
</pattern>
<image id="image0_986_510" width="620" height="360" xlink:href="data:image/png;base64,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"/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -0,0 +1,9 @@
<svg width="310" height="180" viewBox="0 0 310 180" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="310" height="180" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_986_9271" transform="scale(0.0016129 0.00277778)"/>
</pattern>
<image id="image0_986_9271" width="620" height="360" xlink:href="data:image/png;base64,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"/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 104 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 94 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 125 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 110 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,9 @@
<svg width="310" height="180" viewBox="0 0 310 180" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="310" height="180" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_986_950" transform="scale(0.0016129 0.00277778)"/>
</pattern>
<image id="image0_986_950" width="620" height="360" xlink:href="data:image/png;base64,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AADCCTYAgHCCDQAgnGADAAgn2AAAwgk2AIBwgg0AIJxgAwAIJ9gAAMIJNgCAcIINACCcYAMACCfYAADCCTYAgHCCDQAgnGADAAgn2AAAwgk2AIBwgg0AIJxgAwAIJ9gAAMIJNgCAcIINACCcYAMACCfYAADCCTYAgHCCDQAgnGADAAgn2AAAwgk2AIBwgg0AIJxgAwAIJ9gAAMIJNgCAcIINACCcYAMACCfYAADCCTYAgHCCDQAgnGADAAgn2AAAwgk2AIBwgg0AIJxgAwAIJ9gAAMIJNgCAcIINACCcYAMACCfYAADCCTYAgHCCDQAgnGADAAgn2AAAwgk2AIBwgg0AIJxgAwAIJ9gAAMIJNgCAcIINACCcYAMACCfYAADCCTYAgHCCDQAgnGADAAgn2AAAwgk2AIBwgg0AIJxgAwAIJ9gAAMIJNgCAcIINACCcYAMACCfYAADCCTYAgHCCDQAgnGADAAgn2AAAwgk2AIBwgg0AIJxgAwAIJ9gAAMIJNgCAcIINACCcYAMACCfYAADCCTYAgHCCDQAgnGADAAgn2AAAwgk2AIBwgg0AIJxgAwAIJ9gAAMIJNgCAcIINACCcYAMACCfYAADCCTYAgHCCDQAgnGADAAgn2AAAwgk2AIBwgg0AIJxgAwAIJ9gAAMIJNgCAcIINACCcYAMACCfYAADCCTYAgHCCDQAgnGADAAgn2AAAwgk2AIBwgg0AIJxgAwAIJ9gAAMIJNgCAcIINACCcYAMACCfYAADCCTYAgHCCDQAgnGADAAgn2AAAwgk2AIBwgg0AIJxgAwAIJ9gAAMIJNgCAcIINACCcYAMACCfYAADCCTYAgHCCDQAgnGADAAgn2AAAwgk2AIBwgg0AIJxgAwAIJ9gAAMIJNgCAcIINACCcYAMACCfYAADCCTYAgHCL9+/ffyoAAMRyhQ0AINzi7u7OFTYAgGCusAEAhBNsAADhBBsAQLimHMnDblduN9vS931Jsmqa0q4vy+tXr352u9T5j23o8QOGc77JcOrrg/XtZY69vh3tCttmex/5Y3jsuucfak3q/Mc29PgBwznfZDj19cH69jLHXt+OFmzd0xdPNeSHmjz/sflHh8Nyvslx6uuD9e1ljrm+uYcNACCcYAMACCfYAADCHS3YlstFSbVqLqrbJM9/bEOOHzCc802OU18frG8vc8z17WjBdnPdliZwYd//UNv1VXW71PmPbejxA4Zzvslw6uuD9e1ljr2+eZcoAEA497ABAIQTbAAA4QQbAEC4HwCa6lwu6k4UIAAAAABJRU5ErkJggg=="/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,9 @@
<svg width="310" height="180" viewBox="0 0 310 180" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="310" height="180" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_986_9286" transform="scale(0.0016129 0.00277778)"/>
</pattern>
<image id="image0_986_9286" width="620" height="360" xlink:href="data:image/png;base64,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"/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -31,6 +31,7 @@
"ByFirstNameSorting": "First name",
"ByLastNameSorting": "Last name",
"Bytes": "bytes",
"Button": "Button",
"CancelButton": "Cancel",
"ChangeButton": "Change",
"ChangesSavedSuccessfully": "Changes saved successfully",
@ -131,6 +132,7 @@
"ExpiredLink": "Expired link",
"FeedbackAndSupport": "Feedback & Support",
"FillFormButton": "Fill in the form",
"Filter": "Filter",
"FirstName": "First name",
"Free": "Free",
"FreeProFeatures": "Free access to pro features",
@ -261,8 +263,11 @@
"SearchEmptyRoomsDescription": "No rooms match this filter. Try a different one or clear filter to view all rooms.",
"SelectAction": "Select",
"SelectAll": "Select all",
"SelectBackupOnlyFormat": "Select backup only file",
"SelectDOCXFFormat": "Select .DOCXF file",
"SelectDOCXFormat": "Select .DOCX file",
"SelectIMGFormat": "Select IMG file",
"SelectXLSXFormat": "Select .XLSX file",
"SelectFile": "Select file",
"SendButton": "Send",
"SendReport": "Send report",

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