Merge branch 'feature/virtual-rooms-1.2' of github.com:ONLYOFFICE/AppServer into feature/virtual-rooms-1.2

This commit is contained in:
Viktor Fomin 2022-04-21 17:33:16 +03:00
commit d6feda992b
53 changed files with 5751 additions and 96 deletions

View File

@ -53,18 +53,18 @@
},
"files": {
"thirdparty": {
"enable": [ "box", "dropboxv2", "docusign", "google", "onedrive", "sharepoint", "nextcloud", "owncloud", "webdav", "kdrive", "yandex" ]
"enable": [ "box", "dropboxv2", "docusign", "google", "onedrive", "sharepoint", "nextcloud", "owncloud", "webdav", "kdrive", "yandex" ]
},
"docservice": {
"coauthor-docs": [ ".pptx", ".ppsx", ".xlsx", ".csv", ".docx", ".docxf", ".oform", ".txt" ],
"commented-docs": [ ".docx", ".docxf", ".xlsx", ".pptx" ],
"convert-docs": [ ".pptm", ".ppt", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".rtf" ],
"edited-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".csv", ".docx", ".docxf", ".oform", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".txt", ".rtf", ".mht", ".html", ".htm" ],
"encrypted-docs": [ ".docx", ".docxf", ".xlsx", ".pptx", ".oform" ],
"formfilling-docs": [ ".oform" ],
"customfilter-docs": [ ".xlsx" ],
"reviewed-docs": [ ".docx", ".docxf" ],
"viewed-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".gslides", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".gsheet", ".csv", ".docx", ".docxf", ".oform", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".gdoc", ".txt", ".rtf", ".mht", ".html", ".htm", ".epub", ".pdf", ".djvu", ".xps" ],
"coauthor-docs": [ ".pptx", ".ppsx", ".xlsx", ".csv", ".docx", ".docxf", ".oform", ".txt" ],
"commented-docs": [ ".docx", ".docxf", ".xlsx", ".pptx" ],
"convert-docs": [ ".pptm", ".ppt", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".rtf" ],
"edited-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".csv", ".docx", ".docxf", ".oform", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".txt", ".rtf", ".mht", ".html", ".htm" ],
"encrypted-docs": [ ".docx", ".docxf", ".xlsx", ".pptx", ".oform" ],
"formfilling-docs": [ ".oform" ],
"customfilter-docs": [ ".xlsx" ],
"reviewed-docs": [ ".docx", ".docxf" ],
"viewed-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".gslides", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".gsheet", ".csv", ".docx", ".docxf", ".oform", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".gdoc", ".txt", ".rtf", ".mht", ".html", ".htm", ".epub", ".pdf", ".djvu", ".xps" ],
"secret": {
"value": "",
"header": ""
@ -77,15 +77,20 @@
},
"ffmpeg": {
"value": "",
"exts": [ "avi", "mpeg", "mpg", "wmv" ]
"exts": [ "avi", "mpeg", "mpg", "wmv" ]
},
"uploader": {
"chunk-size": 10485760,
"url": "products/files/"
},
"viewed-images": [ ".bmp", ".gif", ".jpeg", ".jpg", ".png", ".ico", ".tif", ".tiff", ".webp" ],
"viewed-media": [ ".aac", ".flac", ".m4a", ".mp3", ".oga", ".ogg", ".wav", ".f4v", ".m4v", ".mov", ".mp4", ".ogv", ".webm" ],
"index": [ ".pptx", ".xlsx", ".docx" ]
"viewed-images": [ ".bmp", ".gif", ".jpeg", ".jpg", ".png", ".ico", ".tif", ".tiff", ".webp" ],
"viewed-media": [ ".aac", ".flac", ".m4a", ".mp3", ".oga", ".ogg", ".wav", ".f4v", ".m4v", ".mov", ".mp4", ".ogv", ".webm" ],
"index": [ ".pptx", ".xlsx", ".docx" ],
"oform": {
"url": "https://oforms.onlyoffice.com/data/reqdata.json",
"period": 60,
"ext": ".oform"
}
},
"web": {
"api": "api/2.0",

View File

@ -298,8 +298,8 @@ export function deleteFolder(folderId, deleteAfter, immediately) {
return request(options);
}
export function createFile(folderId, title, templateId) {
const data = { title, templateId };
export function createFile(folderId, title, templateId, formId) {
const data = { title, templateId, formId };
const options = {
method: "post",
url: `/files/${folderId}/file`,

View File

@ -1,4 +1,5 @@
import { request } from "../client";
import axios from "axios";
export function getSettings() {
return request({
@ -454,3 +455,7 @@ export function toggleTipsSubscription() {
};
return request(options);
}
export function getOforms(url) {
return axios.get(url);
}

View File

@ -107,7 +107,12 @@ const Article = ({
return (
<>
<StyledArticle showText={showText} articleOpen={articleOpen} {...rest}>
<StyledArticle
id={"article-container"}
showText={showText}
articleOpen={articleOpen}
{...rest}
>
<Resizable
defaultSize={{
width: 256,

View File

@ -34,7 +34,13 @@ const StyledWrapper = styled.div`
}
`;
const TilesLoader = ({ foldersCount, filesCount, sectionWidth, ...rest }) => {
const TilesLoader = ({
foldersCount,
filesCount,
sectionWidth,
withTitle,
...rest
}) => {
const folders = [];
const files = [];
@ -59,15 +65,17 @@ const TilesLoader = ({ foldersCount, filesCount, sectionWidth, ...rest }) => {
) : null}
<StyledTilesLoader>{folders}</StyledTilesLoader>
{filesCount > 0 ? (
<RectangleLoader
height="22px"
width="35px"
className="files"
animate
{...rest}
/>
) : null}
{filesCount > 0
? withTitle && (
<RectangleLoader
height="22px"
width="35px"
className="files"
animate
{...rest}
/>
)
: null}
<StyledTilesLoader>{files}</StyledTilesLoader>
</StyledWrapper>
);
@ -81,6 +89,7 @@ TilesLoader.propTypes = {
TilesLoader.defaultProps = {
foldersCount: 2,
filesCount: 8,
withTitle: true,
};
export default TilesLoader;

View File

@ -38,10 +38,8 @@ const StyledSectionContainer = styled.section`
display: flex;
flex-direction: column;
width: ${(props) =>
props.infoPanelIsVisible ? "calc(100% - 677px)" : "100%"};
max-width: ${(props) =>
props.infoPanelIsVisible ? "calc(100vw - 677px)" : "100vw"};
width: 100%;
max-width: 100%;
@media ${tablet} {
width: 100%;

View File

@ -323,6 +323,12 @@ class AuthStore {
setProviders = (providers) => {
this.providers = providers;
};
getOforms = () => {
const culture =
this.userStore.user.cultureName || this.settingsStore.culture;
return api.settings.getOforms(`${this.settingsStore.urlOforms}${culture}`);
};
}
export default new AuthStore();

View File

@ -52,6 +52,8 @@ class SettingsStore {
enabledJoin = false;
urlLicense = "https://gnu.org/licenses/gpl-3.0.html";
urlSupport = "https://helpdesk.onlyoffice.com/";
urlOforms = "https://cmsoforms.onlyoffice.com/api/oforms?populate=*&locale=";
logoUrl = combineUrl(proxyURL, "/static/images/nav.logo.opened.react.svg");
customNames = {
id: "Common",

View File

@ -0,0 +1,15 @@
<svg width="150" height="150" viewBox="0 0 150 150" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_964_170806)">
<path d="M71.4219 20.8414C72.779 18.6071 76.0175 18.595 77.3912 20.8191L144.306 129.161C145.746 131.493 144.069 134.5 141.328 134.5H21.1846H8.6095C5.88051 134.5 4.20137 131.516 5.61805 129.183L71.4219 20.8414Z" fill="url(#paint0_linear_964_170806)" stroke="#333333"/>
<path d="M84.6797 70.3176C84.6797 74.9563 83.6221 80.7454 81.5068 87.6848C81.0244 89.2434 80.5605 90.802 80.1152 92.3606C79.6699 93.9192 79.1875 95.4963 78.668 97.092C77.5547 100.506 76.5342 102.157 75.6064 102.046C74.6045 101.898 73.4912 100.172 72.2666 96.8694C72.0439 96.3128 71.6729 95.2737 71.1533 93.7522C70.6709 92.2307 70.0215 90.2083 69.2051 87.6848C67.0898 80.968 66.0322 75.3274 66.0322 70.763C66.0322 62.8958 69.1494 58.8694 75.3838 58.6838C81.5811 58.5725 84.6797 62.4505 84.6797 70.3176ZM82.4531 111.954C82.4531 113.81 81.8408 115.443 80.6162 116.853C79.2803 118.374 77.5361 119.135 75.3838 119.135C73.1201 119.135 71.3389 118.43 70.04 117.02C68.8154 115.647 68.2031 113.995 68.2031 112.066C68.2031 110.099 68.8154 108.41 70.04 107C71.3018 105.59 73.0273 104.885 75.2168 104.885C77.5176 104.885 79.2988 105.59 80.5605 107C81.8223 108.448 82.4531 110.099 82.4531 111.954Z" fill="white"/>
</g>
<defs>
<linearGradient id="paint0_linear_964_170806" x1="-0.598706" y1="31.0226" x2="80.71" y2="121.735" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFC671"/>
<stop offset="1" stop-color="#FF6F3D"/>
</linearGradient>
<clipPath id="clip0_964_170806">
<rect width="150" height="150" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,16 @@
<svg width="76" height="76" viewBox="0 0 76 76" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M26.7475 1L26.7473 1C12.5292 1.00505 1 12.5342 1 26.7574C1 40.9807 12.5293 52.505 26.7475 52.505C40.9708 52.505 52.5 40.9757 52.5 26.7525C52.5 12.5292 40.9708 1 26.7475 1Z" fill="#BDECFF" stroke="#333333"/>
<path d="M26.5 47.5C37.8218 47.5 47 38.3218 47 27C47 15.6782 37.8218 6.5 26.5 6.5C15.1782 6.5 6 15.6782 6 27C6 38.3218 15.1782 47.5 26.5 47.5Z" fill="#FEFEFE" stroke="#333333"/>
<path d="M45.8722 44.065L45.5186 43.7115L45.1651 44.0651L43.0649 46.1656L42.7113 46.5192L43.0649 46.8728L49.5323 53.3392L49.8859 53.6928L50.2394 53.3392L52.3396 51.2386L52.6932 50.885L52.3396 50.5315L45.8722 44.065Z" fill="#FEFEFE" stroke="#333333"/>
<mask id="path-4-inside-1_962_171010" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M49.9405 55.74L69.0133 74.8079C69.7683 75.5629 71.0051 75.5629 71.76 74.8079L73.861 72.7069C74.616 71.952 74.616 70.7152 73.861 69.9602L54.7882 50.8874C54.0332 50.1324 52.7964 50.1324 52.0415 50.8874L49.9405 52.9884C49.1855 53.7483 49.1855 54.9851 49.9405 55.74Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M49.9405 55.74L69.0133 74.8079C69.7683 75.5629 71.0051 75.5629 71.76 74.8079L73.861 72.7069C74.616 71.952 74.616 70.7152 73.861 69.9602L54.7882 50.8874C54.0332 50.1324 52.7964 50.1324 52.0415 50.8874L49.9405 52.9884C49.1855 53.7483 49.1855 54.9851 49.9405 55.74Z" fill="url(#paint0_linear_962_171010)"/>
<path d="M49.9405 55.74L49.2334 56.4471L49.2335 56.4472L49.9405 55.74ZM69.0133 74.8079L69.7204 74.1008L69.7204 74.1007L69.0133 74.8079ZM71.76 74.8079L72.4671 75.515V75.515L71.76 74.8079ZM73.861 72.7069L73.1539 71.9998L73.1539 71.9998L73.861 72.7069ZM73.861 69.9602L73.1539 70.6673L73.861 69.9602ZM54.7882 50.8874L54.0811 51.5945L54.7882 50.8874ZM52.0415 50.8874L52.7486 51.5945V51.5945L52.0415 50.8874ZM49.9405 52.9884L49.2334 52.2813L49.2311 52.2836L49.9405 52.9884ZM49.2335 56.4472L68.3063 75.5151L69.7204 74.1007L50.6475 55.0328L49.2335 56.4472ZM68.3062 75.515C69.4517 76.6605 71.3216 76.6605 72.4671 75.515L71.0529 74.1008C70.6885 74.4653 70.0849 74.4653 69.7204 74.1008L68.3062 75.515ZM72.4671 75.515L74.5681 73.414L73.1539 71.9998L71.0529 74.1008L72.4671 75.515ZM74.5681 73.414C75.7136 72.2685 75.7136 70.3986 74.5681 69.2531L73.1539 70.6673C73.5183 71.0318 73.5183 71.6354 73.1539 71.9998L74.5681 73.414ZM74.5681 69.2531L55.4953 50.1803L54.0811 51.5945L73.1539 70.6673L74.5681 69.2531ZM55.4953 50.1803C54.3498 49.0348 52.4799 49.0348 51.3344 50.1803L52.7486 51.5945C53.113 51.2301 53.7166 51.2301 54.0811 51.5945L55.4953 50.1803ZM51.3344 50.1803L49.2334 52.2813L50.6476 53.6955L52.7486 51.5945L51.3344 50.1803ZM49.2311 52.2836C48.0899 53.4322 48.0866 55.3004 49.2334 56.4471L50.6476 55.0329C50.2844 54.6698 50.2811 54.0644 50.6499 53.6932L49.2311 52.2836Z" fill="#333333" mask="url(#path-4-inside-1_962_171010)"/>
<defs>
<linearGradient id="paint0_linear_962_171010" x1="45.9493" y1="45.2847" x2="64.303" y2="65.5008" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFC671"/>
<stop offset="1" stop-color="#FF6F3D"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,7 @@
{
"GalleryEmptyScreenHeader": "Failed to load form templates",
"GalleryEmptyScreenDescription": "Select any form template to see the details",
"EmptyScreenDescription": "Please check your Internet connection and refresh the page or try later",
"TemplateInfo": "Template info",
"FormTemplateInfo": "Form template info"
}

View File

@ -0,0 +1,7 @@
{
"GalleryEmptyScreenHeader": "Не удалось загрузить шаблоны форм",
"GalleryEmptyScreenDescription": "Выберите шаблон формы, чтобы просмотреть подробности",
"EmptyScreenDescription": "Проверьте подключение к Интернету и обновите страницу или повторите попытку позже",
"TemplateInfo": "Информация шаблона",
"FormTemplateInfo": "Информация о шаблоне формы"
}

View File

@ -1,7 +1,7 @@
import React from "react";
import { Provider as FilesProvider } from "mobx-react";
import { inject, observer } from "mobx-react";
import { Switch } from "react-router-dom";
import { Switch, withRouter } from "react-router-dom";
import config from "../package.json";
import PrivateRoute from "@appserver/common/components/PrivateRoute";
import AppLoader from "@appserver/common/components/AppLoader";
@ -28,6 +28,7 @@ import {
ArticleHeaderContent,
ArticleMainButtonContent,
} from "./components/Article";
import FormGallery from "./pages/FormGallery";
const { proxyURL } = AppServerConfig;
const homepage = config.homepage;
@ -40,6 +41,10 @@ const HISTORY_URL = combineUrl(PROXY_HOMEPAGE_URL, "/:fileId/history");
const PRIVATE_ROOMS_URL = combineUrl(PROXY_HOMEPAGE_URL, "/private");
const FILTER_URL = combineUrl(PROXY_HOMEPAGE_URL, "/filter");
const MEDIA_VIEW_URL = combineUrl(PROXY_HOMEPAGE_URL, "/#preview");
const FORM_GALLERY_URL = combineUrl(
PROXY_HOMEPAGE_URL,
"/form-gallery/:folderId"
);
if (!window.AppServer) {
window.AppServer = {};
@ -56,8 +61,12 @@ window.AppServer.files = {
const Error404 = React.lazy(() => import("studio/Error404"));
const FilesArticle = React.memo(() => {
return (
const FilesArticle = React.memo(({ history }) => {
const isFormGallery = history.location.pathname
.split("/")
.includes("form-gallery");
return !isFormGallery ? (
<Article>
<Article.Header>
<ArticleHeaderContent />
@ -69,6 +78,8 @@ const FilesArticle = React.memo(() => {
<ArticleBodyContent />
</Article.Body>
</Article>
) : (
<></>
);
});
@ -81,6 +92,7 @@ const FilesSection = React.memo(() => {
<PrivateRoute exact path={HOME_URL} component={Home} />
<PrivateRoute path={FILTER_URL} component={Home} />
<PrivateRoute path={MEDIA_VIEW_URL} component={Home} />
<PrivateRoute path={FORM_GALLERY_URL} component={FormGallery} />
<PrivateRoute component={Error404Route} />
</Switch>
);
@ -158,7 +170,7 @@ class FilesContent extends React.Component {
return (
<>
<Panels />
<FilesArticle />
<FilesArticle history={this.props.history} />
<FilesSection />
</>
);
@ -182,7 +194,7 @@ const Files = inject(({ auth, filesStore }) => {
auth.setProductVersion(config.version);
},
};
})(withTranslation("Common")(observer(FilesContent)));
})(withTranslation("Common")(observer(withRouter(FilesContent))));
export default () => (
<FilesProvider {...stores}>

View File

@ -20,12 +20,19 @@ export default function withContent(WrappedContent) {
constructor(props) {
super(props);
const { item, fileActionId, fileActionExt, fileActionTemplateId } = props;
const {
item,
fileActionId,
fileActionExt,
fileActionTemplateId,
fromTemplate,
} = props;
let titleWithoutExt = props.titleWithoutExt;
if (
fileActionId === -1 &&
item.id === fileActionId &&
fileActionTemplateId === null
fileActionTemplateId === null &&
!fromTemplate
) {
titleWithoutExt = getDefaultFileName(fileActionExt);
}
@ -188,8 +195,11 @@ export default function withContent(WrappedContent) {
clearActiveOperations,
addActiveItems,
fileCopyAs,
fromTemplate,
gallerySelected,
} = this.props;
const { itemTitle } = this.state;
const { parentId, fileExst } = item;
const isMakeFormFromFile = fileActionTemplateId ? true : false;
@ -288,6 +298,29 @@ export default function withContent(WrappedContent) {
clearActiveOperations(fileIds);
return setIsLoading(false);
});
} else if (fromTemplate) {
createFile(
parentId,
`${itemTitle}.${fileExst}`,
undefined,
gallerySelected.id
)
.then((file) => {
createdFileId = file.id;
addActiveItems([file.id]);
return open && openDocEditor(file.id, file.providerKey, tab);
})
.then(() => this.completeAction(itemId))
.catch((e) => toastr.error(e))
.finally(() => {
const fileIds = [+itemId];
createdFileId && fileIds.push(createdFileId);
clearActiveOperations(fileIds);
return setIsLoading(false);
});
} else {
@ -465,6 +498,7 @@ export default function withContent(WrappedContent) {
isUpdatingRowItem,
passwordEntryProcess,
addActiveItems,
gallerySelected,
} = filesStore;
const { clearActiveOperations, fileCopyAs } = uploadDataStore;
const { isRecycleBinFolder, isPrivacyFolder } = treeFoldersStore;
@ -474,6 +508,7 @@ export default function withContent(WrappedContent) {
id: fileActionId,
templateId: fileActionTemplateId,
type: fileActionType,
fromTemplate,
} = filesStore.fileActionStore;
const { replaceFileStream, setEncryptionAccess } = auth;
const {
@ -491,7 +526,7 @@ export default function withContent(WrappedContent) {
const isEdit =
item.id === fileActionId && item.fileExst === fileActionExt;
const titleWithoutExt = getTitleWithoutExst(item);
const titleWithoutExt = getTitleWithoutExst(item, fromTemplate);
return {
createFile,
@ -526,6 +561,8 @@ export default function withContent(WrappedContent) {
fileCopyAs,
isEdit,
titleWithoutExt,
fromTemplate,
gallerySelected,
};
}
)(observer(WithContent));

View File

@ -10,10 +10,13 @@ import {
isTablet as isTabletUtils,
} from "@appserver/components/utils/device";
import Loaders from "@appserver/common/components/Loaders";
import { FileAction } from "@appserver/common/constants";
import { AppServerConfig, FileAction } from "@appserver/common/constants";
import { encryptionUploadDialog } from "../../../helpers/desktop";
import { withRouter } from "react-router";
import MobileView from "./MobileView";
import { combineUrl } from "@appserver/common/utils";
import config from "../../../../package.json";
import withLoader from "../../../HOCs/withLoader";
const ArticleMainButtonContent = (props) => {
@ -35,6 +38,9 @@ const ArticleMainButtonContent = (props) => {
isRecentFolder,
isCommonFolder,
isRecycleBinFolder,
history,
hasGalleryFiles,
currentFolderId,
} = props;
const inputFilesElement = React.useRef(null);
const inputFolderElement = React.useRef(null);
@ -89,6 +95,16 @@ const ArticleMainButtonContent = (props) => {
const onInputClick = React.useCallback((e) => (e.target.value = null), []);
const onShowGallery = () => {
history.push(
combineUrl(
AppServerConfig.proxyURL,
config.homepage,
`/form-gallery/${currentFolderId}/`
)
);
};
React.useEffect(() => {
const folderUpload = !isMobile
? [
@ -126,6 +142,13 @@ const ArticleMainButtonContent = (props) => {
disabled: isPrivacy,
key: "form-file",
},
hasGalleryFiles && {
className: "main-button_drop-down_sub",
label: t("Common:OFORMsGallery"),
onClick: onShowGallery,
disabled: isPrivacy,
key: "form-gallery",
},
],
},
]
@ -148,6 +171,17 @@ const ArticleMainButtonContent = (props) => {
},
];
if ((isMobile || isTabletUtils()) && hasGalleryFiles) {
formActions.push({
className: "main-button_drop-down_sub",
icon: "images/form.react.svg",
label: t("Common:OFORMsGallery"),
onClick: onShowGallery,
disabled: isPrivacy,
key: "form-gallery",
});
}
const actions = [
{
className: "main-button_drop-down",
@ -209,6 +243,8 @@ const ArticleMainButtonContent = (props) => {
}, [
t,
isPrivacy,
hasGalleryFiles,
currentFolderId,
onCreate,
onShowSelectFileDialog,
onUploadFileClick,
@ -268,13 +304,21 @@ const ArticleMainButtonContent = (props) => {
};
export default inject(
({ auth, filesStore, dialogsStore, uploadDataStore, treeFoldersStore }) => {
({
auth,
filesStore,
dialogsStore,
uploadDataStore,
treeFoldersStore,
selectedFolderStore,
}) => {
const {
isLoaded,
firstLoad,
isLoading,
fileActionStore,
canCreate,
hasGalleryFiles,
} = filesStore;
const {
isPrivacyFolder,
@ -289,6 +333,8 @@ export default inject(
const isArticleLoading = (!isLoaded || isLoading) && firstLoad;
const currentFolderId = selectedFolderStore.id;
return {
showText: auth.settingsStore.showText,
isMobileArticle: auth.settingsStore.isMobileArticle,
@ -310,10 +356,14 @@ export default inject(
isLoading,
isLoaded,
firstLoad,
hasGalleryFiles,
currentFolderId,
};
}
)(
withTranslation(["Article", "Common"])(
withLoader(observer(ArticleMainButtonContent))(<Loaders.ArticleButton />)
withLoader(observer(withRouter(ArticleMainButtonContent)))(
<Loaders.ArticleButton />
)
)
);

View File

@ -32,8 +32,8 @@ export const getAccessIcon = (access) => {
}
};
export const getTitleWithoutExst = (item) => {
return item.fileExst
export const getTitleWithoutExst = (item, fromTemplate) => {
return item.fileExst && !fromTemplate
? item.title.split(".").slice(0, -1).join(".")
: item.title;
};

View File

@ -0,0 +1,58 @@
import React, { useEffect } from "react";
import { observer, inject } from "mobx-react";
import EmptyScreenContainer from "@appserver/components/empty-screen-container";
import { withTranslation } from "react-i18next";
import TileContainer from "./TilesView/sub-components/TileContainer";
import FileTile from "./TilesView/FileTile";
import Loaders from "@appserver/common/components/Loaders";
const SectionBodyContent = ({
oformFiles,
hasGalleryFiles,
setGallerySelected,
t,
tReady,
}) => {
const onMouseDown = (e) => {
if (
e.target.closest(".scroll-body") &&
!e.target.closest(".files-item") &&
!e.target.closest(".not-selectable") &&
!e.target.closest(".info-panel")
) {
setGallerySelected(null);
}
};
useEffect(() => {
window.addEventListener("mousedown", onMouseDown);
setGallerySelected(null);
return () => {
window.removeEventListener("mousedown", onMouseDown);
};
}, [onMouseDown]);
return !tReady || !oformFiles ? (
<Loaders.Tiles foldersCount={0} withTitle={false} />
) : !hasGalleryFiles ? (
<EmptyScreenContainer
imageSrc="images/empty_screen_form-gallery.react.svg"
imageAlt="Empty Screen Gallery image"
headerText={t("GalleryEmptyScreenHeader")}
descriptionText={t("EmptyScreenDescription")}
/>
) : (
<TileContainer useReactWindow={false} className="tile-container">
{oformFiles.map((item, index) => (
<FileTile key={`${item.id}_${index}`} item={item} />
))}
</TileContainer>
);
};
export default inject(({ filesStore }) => ({
oformFiles: filesStore.oformFiles,
hasGalleryFiles: filesStore.hasGalleryFiles,
setGallerySelected: filesStore.setGallerySelected,
}))(withTranslation("FormGallery")(observer(SectionBodyContent)));

View File

@ -0,0 +1,68 @@
import React from "react";
import { inject, observer } from "mobx-react";
import IconButton from "@appserver/components/icon-button";
import { withTranslation } from "react-i18next";
import { withRouter } from "react-router-dom";
import { AppServerConfig } from "@appserver/common/constants";
import {
StyledHeadline,
StyledContainer,
StyledInfoPanelToggleWrapper,
} from "./StyledGallery";
import config from "../../../package.json";
import FilesFilter from "@appserver/common/api/files/filter";
import { combineUrl } from "@appserver/common/utils";
const SectionHeaderContent = (props) => {
const { t, history, match, isInfoPanelVisible, toggleInfoPanel } = props;
const onBackToFiles = () => {
const filter = FilesFilter.getDefault();
filter.folder = match.params.folderId;
const urlFilter = filter.toUrlParams();
history.push(
combineUrl(
AppServerConfig.proxyURL,
config.homepage,
`/filter?${urlFilter}`
)
);
};
return (
<StyledContainer>
<IconButton
iconName="/static/images/arrow.path.react.svg"
size="17"
isFill
onClick={onBackToFiles}
className="arrow-button"
/>
<StyledHeadline type="content" truncate>
{t("Common:OFORMsGallery")}
</StyledHeadline>
<StyledInfoPanelToggleWrapper isInfoPanelVisible={isInfoPanelVisible}>
<div className="info-panel-toggle-bg">
<IconButton
className="info-panel-toggle"
iconName="images/panel.react.svg"
size="16"
isFill={true}
onClick={toggleInfoPanel}
/>
</div>
</StyledInfoPanelToggleWrapper>
</StyledContainer>
);
};
export default inject(({ infoPanelStore }) => {
const { toggleIsVisible, isVisible } = infoPanelStore;
return {
toggleInfoPanel: toggleIsVisible,
isInfoPanelVisible: isVisible,
};
})(withTranslation("Common")(withRouter(observer(SectionHeaderContent))));

View File

@ -0,0 +1,81 @@
import styled, { css } from "styled-components";
import { isMobile, isMobileOnly } from "react-device-detect";
import { tablet, mobile } from "@appserver/components/utils/device";
import Headline from "@appserver/common/components/Headline";
import { Base } from "@appserver/components/themes";
const StyledHeadline = styled(Headline)`
width: calc(100% + 1px);
font-weight: 700;
font-size: ${isMobile ? "21px !important" : "18px"};
line-height: ${isMobile ? "28px !important" : "24px"};
@media ${tablet} {
font-size: 21px;
line-height: 28px;
}
`;
const StyledContainer = styled.div`
width: 100%;
padding: 14px 0 0px;
display: grid;
grid-template-columns: ${(props) =>
props.isRootFolder ? "1fr auto" : "29px 1fr auto"};
align-items: center;
.arrow-button {
width: 17px;
min-width: 17px;
}
@media ${tablet} {
width: 100%;
padding: 16px 0 0px;
}
${isMobile &&
css`
width: 100% !important;
padding: 16px 0 0px;
`}
@media ${mobile} {
width: 100%;
padding: 12px 0 0;
}
${isMobileOnly &&
css`
width: 100% !important;
padding: 12px 0 0;
`}
`;
const StyledInfoPanelToggleWrapper = styled.div`
margin-left: auto;
.info-panel-toggle-bg {
height: 32px;
width: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: ${(props) =>
props.isInfoPanelVisible
? props.theme.infoPanel.sectionHeaderToggleBgActive
: props.theme.infoPanel.sectionHeaderToggleBg};
path {
fill: ${(props) =>
props.isInfoPanelVisible
? props.theme.infoPanel.sectionHeaderToggleIconActive
: props.theme.infoPanel.sectionHeaderToggleIcon};
}
}
`;
StyledInfoPanelToggleWrapper.defaultProps = { theme: Base };
export { StyledHeadline, StyledContainer, StyledInfoPanelToggleWrapper };

View File

@ -0,0 +1,35 @@
import React from "react";
import Tile from "./sub-components/Tile";
import { SimpleFilesTileContent } from "./StyledTileView";
import Link from "@appserver/components/link";
import { isDesktop } from "react-device-detect";
const FileTile = (props) => {
const { item } = props;
return (
<div ref={props.selectableRef}>
<Tile key={item.id} item={item}>
<SimpleFilesTileContent
//sideColor={theme.filesSection.tilesView.sideColor}
>
<Link
className="item-file-name"
containerWidth="100%"
type="page"
fontWeight="600"
fontSize={isDesktop ? "13px" : "14px"}
target="_blank"
//{...linkStyles} //TODO: OFORM
//color={theme.filesSection.tilesView.color}
isTextOverflow
>
{item.attributes.name_form}
</Link>
</SimpleFilesTileContent>
</Tile>
</div>
);
};
export default FileTile;

View File

@ -0,0 +1,402 @@
import styled, { css } from "styled-components";
import { Base } from "@appserver/components/themes";
import TileContent from "./sub-components/TileContent";
import { tablet, desktop } from "@appserver/components/utils/device";
import { isMobile } from "react-device-detect";
const FlexBoxStyles = css`
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
align-content: center;
`;
const checkedStyle = css`
background: ${(props) =>
props.theme.filesSection.tilesView.tile.checkedColor} !important;
`;
const StyledTile = styled.div`
box-sizing: border-box;
width: 100%;
border: ${(props) => props.theme.filesSection.tilesView.tile.border};
border-radius: 6px;
${(props) => props.showHotkeyBorder && "border-color: #2DA7DB"};
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
${(props) => props.isSelected && checkedStyle}
&:before,
&:after {
${(props) => props.showHotkeyBorder && "border-color: #2DA7DB"};
}
&:before,
&:after {
${(props) => props.isSelected && checkedStyle};
}
.file-icon {
display: flex;
flex: 0 0 auto;
user-select: none;
}
.file-icon_container {
width: 32px;
height: 32px;
margin-left: 16px;
margin-right: 8px;
}
`;
const StyledFileTileTop = styled.div`
${FlexBoxStyles};
background: ${(props) =>
props.theme.filesSection.tilesView.tile.backgroundColorTop};
justify-content: space-between;
align-items: baseline;
height: 156px;
overflow: hidden;
position: relative;
border-radius: 6px 6px 0 0;
.thumbnail-image-link {
margin: 0 auto;
.thumbnail-image {
pointer-events: none;
position: relative;
height: 100%;
width: 100%;
object-fit: cover;
border-radius: 6px 6px 0 0;
z-index: 0;
}
}
.temporary-icon > .injected-svg {
position: absolute;
width: 100%;
bottom: 16px;
}
`;
const StyledFileTileBottom = styled.div`
${FlexBoxStyles};
${(props) => !props.isEdit && props.isSelected && checkedStyle}
border-top: 1px solid transparent;
${(props) =>
props.isSelected &&
css`
border-top: ${(props) => props.theme.filesSection.tilesView.tile.border};
border-radius: 0 0 6px 6px;
`}
padding: 9px 0;
height: 62px;
box-sizing: border-box;
.tile-file-loader {
padding-top: 4px;
padding-left: 3px;
}
`;
const StyledContent = styled.div`
display: flex;
flex-basis: 100%;
a {
display: block;
display: -webkit-box;
max-width: 400px;
height: auto;
margin: 0 auto;
line-height: 19px;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
word-break: break-word;
}
@media (max-width: 1024px) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
`;
const StyledElement = styled.div`
flex: 0 0 auto;
display: flex;
margin-right: 4px;
user-select: none;
margin-top: 3px;
height: 32px;
width: 32px;
`;
const StyledOptionButton = styled.div`
display: block;
.expandButton > div:first-child {
padding: 8px 21px 8px 12px;
}
`;
StyledOptionButton.defaultProps = { theme: Base };
const SimpleFilesTileContent = styled(TileContent)`
.row-main-container {
height: auto;
max-width: 100%;
align-self: flex-end;
}
.main-icons {
align-self: flex-end;
}
.badge {
margin-right: 8px;
cursor: pointer;
height: 16px;
width: 16px;
}
.new-items {
position: absolute;
right: 29px;
top: 19px;
}
.badges {
display: flex;
align-items: center;
}
.share-icon {
margin-top: -4px;
padding-right: 8px;
}
.favorite,
.can-convert,
.edit {
svg:not(:root) {
width: 14px;
height: 14px;
}
}
@media (max-width: 1024px) {
display: inline-flex;
height: auto;
& > div {
margin-top: 0;
}
}
`;
const paddingCss = css`
@media ${desktop} {
margin-left: 1px;
padding-right: 3px;
}
@media ${tablet} {
margin-left: -1px;
}
`;
const StyledGridWrapper = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(216px, 1fr));
width: 100%;
margin-bottom: ${(props) => (props.isFolders ? "23px" : 0)};
box-sizing: border-box;
${paddingCss};
grid-gap: 14px 16px;
@media ${tablet} {
grid-gap: 14px;
}
`;
const StyledTileContainer = styled.div`
position: relative;
.tile-item-wrapper {
position: relative;
width: 100%;
&.file {
padding: 0;
}
&.folder {
padding: 0;
}
}
.tile-items-heading {
margin: 0;
margin-bottom: 15px;
display: flex;
align-items: center;
justify-content: space-between;
div {
cursor: pointer !important;
.sort-combo-box {
margin-right: 3px;
.dropdown-container {
top: 104%;
bottom: auto;
min-width: 200px;
margin-top: 3px;
.option-item {
display: flex;
align-items: center;
justify-content: space-between;
min-width: 200px;
svg {
width: 16px;
height: 16px;
}
.option-item__icon {
display: none;
cursor: pointer;
${(props) =>
props.isDesc &&
css`
transform: rotate(180deg);
`}
path {
fill: ${(props) => props.theme.filterInput.sort.sortFill};
}
}
:hover {
.option-item__icon {
display: flex;
}
}
}
.selected-option-item {
background: ${(props) =>
props.theme.filterInput.sort.hoverBackground};
cursor: auto;
.selected-option-item__icon {
display: flex;
}
}
}
.optionalBlock {
display: flex;
flex-direction: row;
align-items: center;
font-size: 12px;
font-weight: 600;
color: ${(props) => props.theme.filterInput.sort.tileSortColor};
.sort-icon {
margin-right: 8px;
svg {
path {
fill: ${(props) => props.theme.filterInput.sort.tileSortFill};
}
}
}
}
.combo-buttons_arrow-icon {
display: none;
}
}
}
}
@media ${tablet} {
margin-right: -3px;
}
${isMobile &&
css`
padding-top: 24px;
`}
`;
StyledTileContainer.defaultProps = { theme: Base };
const truncateCss = css`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;
const commonCss = css`
margin: 0;
font-family: "Open Sans";
font-size: 12px;
font-style: normal;
font-weight: 600;
`;
const StyledTileContent = styled.div`
width: 100%;
display: inline-flex;
`;
const MainContainerWrapper = styled.div`
${commonCss};
display: flex;
align-self: center;
margin-right: auto;
`;
const MainContainer = styled.div`
height: 20px;
@media (max-width: 1024px) {
${truncateCss};
}
`;
export {
StyledTile,
StyledFileTileTop,
StyledFileTileBottom,
StyledContent,
StyledElement,
StyledOptionButton,
SimpleFilesTileContent,
StyledGridWrapper,
StyledTileContainer,
StyledTileContent,
MainContainerWrapper,
MainContainer,
};

View File

@ -0,0 +1,226 @@
import React from "react";
import { inject, observer } from "mobx-react";
import ContextMenuButton from "@appserver/components/context-menu-button";
import PropTypes from "prop-types";
import ContextMenu from "@appserver/components/context-menu";
import { isDesktop } from "react-device-detect";
import Link from "@appserver/components/link";
import { withTranslation } from "react-i18next";
import { ReactSVG } from "react-svg";
import { AppServerConfig } from "@appserver/common/constants";
import { combineUrl } from "@appserver/common/utils";
import config from "../../../../../package.json";
import FilesFilter from "@appserver/common/api/files/filter";
import { withRouter } from "react-router-dom";
import {
StyledTile,
StyledFileTileTop,
StyledFileTileBottom,
StyledContent,
StyledOptionButton,
} from "../StyledTileView";
class Tile extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
errorLoadSrc: false,
};
this.cm = React.createRef();
this.tile = React.createRef();
}
onError = () => {
this.setState({
errorLoadSrc: true,
});
};
getIconFile = () => {
const { thumbnailClick, item } = this.props;
//const src = item.attributes.card_prewiew.data.attributes.formats.thumbnail.url;
const src = item.attributes.card_prewiew.data.attributes.url;
const svgLoader = () => <div style={{ width: "96px" }} />;
return src ? (
<Link
className="thumbnail-image-link"
type="page"
onClick={thumbnailClick}
>
<img
src={src}
className="thumbnail-image"
alt="Thumbnail-img"
onError={this.onError}
/>
</Link>
) : (
<ReactSVG className="temporary-icon" src={src} loading={svgLoader} />
);
};
onFileIconClick = () => {
if (isDesktop) return;
const { onSelect, item } = this.props;
onSelect && onSelect(true, item);
};
getContextModel = () => {
return [
{
key: "create",
label: this.props.t("Common:Create"),
onClick: this.onCreateForm,
},
{
key: "template-info",
label: this.props.t("TemplateInfo"),
onClick: this.onShowTemplateInfo,
},
];
};
onCreateForm = () => {
const { match, history } = this.props;
const { setInfoPanelIsVisible } = this.props;
const filter = FilesFilter.getDefault();
filter.folder = match.params.folderId;
const urlFilter = filter.toUrlParams();
setInfoPanelIsVisible(false);
history.push(
combineUrl(
AppServerConfig.proxyURL,
config.homepage,
`/filter?${urlFilter}`
)
);
};
onShowTemplateInfo = () => {
if (!this.props.isInfoPanelVisible) {
this.props.setInfoPanelIsVisible(true);
}
};
getOptions = () => ["create", "template-info"];
onSelectForm = () => {
this.props.setGallerySelected(this.props.item);
};
render() {
const {
children,
contextButtonSpacerWidth,
tileContextClick,
isActive,
isSelected,
title,
showHotkeyBorder,
getIcon,
} = this.props;
const src = getIcon(32, ".docxf");
const element = <img className="react-svg-icon" src={src} />;
const onContextMenu = (e) => {
tileContextClick && tileContextClick();
if (!this.cm.current.menuRef.current) {
this.tile.current.click(e); //TODO: need fix context menu to global
}
this.cm.current.show(e);
};
const icon = this.getIconFile();
//TODO: OFORM isActive
return (
<StyledTile
ref={this.tile}
isSelected={isSelected}
onContextMenu={onContextMenu}
isActive={isActive}
isDesktop={isDesktop}
showHotkeyBorder={showHotkeyBorder}
onDoubleClick={this.onCreateForm}
onClick={this.onSelectForm}
className="files-item"
>
<StyledFileTileTop isActive={isActive}>{icon}</StyledFileTileTop>
<StyledFileTileBottom isSelected={isSelected} isActive={isActive}>
<div className="file-icon_container">
<div className="file-icon" onClick={this.onFileIconClick}>
{element}
</div>
</div>
<StyledContent>{children}</StyledContent>
<StyledOptionButton spacerWidth={contextButtonSpacerWidth}>
<ContextMenuButton
className="expandButton"
directionX="right"
getData={this.getOptions}
isNew={true}
onClick={onContextMenu}
title={title}
/>
<ContextMenu
getContextModel={this.getContextModel}
ref={this.cm}
withBackdrop={true}
/>
</StyledOptionButton>
</StyledFileTileBottom>
</StyledTile>
);
}
}
Tile.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.element),
PropTypes.element,
]),
className: PropTypes.string,
contextButtonSpacerWidth: PropTypes.string,
contextOptions: PropTypes.array,
data: PropTypes.object,
id: PropTypes.string,
onSelect: PropTypes.func,
tileContextClick: PropTypes.func,
};
Tile.defaultProps = {
contextButtonSpacerWidth: "32px",
item: {},
};
export default inject(
({ filesStore, settingsStore, infoPanelStore }, { item }) => {
const { gallerySelected, setGallerySelected } = filesStore;
const { getIcon } = settingsStore;
const { setInfoPanelIsVisible, isVisible } = infoPanelStore;
const isSelected = item.id === gallerySelected?.id;
return {
isSelected,
setGallerySelected,
getIcon,
setInfoPanelIsVisible,
isInfoPanelVisible: isVisible,
};
}
)(withTranslation(["FormGallery", "Common"])(withRouter(observer(Tile))));

View File

@ -0,0 +1,79 @@
import React, { memo } from "react";
import { withTranslation } from "react-i18next";
import PropTypes from "prop-types";
import { FixedSizeList as List, areEqual } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
import CustomScrollbarsVirtualList from "@appserver/components/scrollbar";
import { StyledGridWrapper, StyledTileContainer } from "../StyledTileView";
class TileContainer extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
contextOptions: [],
};
}
renderTile = memo(({ data, index, style }) => {
return <div style={style}>{data[index]}</div>;
}, areEqual);
render() {
const {
itemHeight,
children,
useReactWindow,
id,
className,
style,
} = this.props;
const renderList = ({ height, width }) => (
<List
className="list"
height={height}
width={width}
itemSize={itemHeight}
itemCount={children.length}
itemData={children}
outerElementType={CustomScrollbarsVirtualList}
>
{this.renderTile}
</List>
);
return (
<StyledTileContainer
id={id}
className={className}
style={style}
useReactWindow={useReactWindow}
>
{useReactWindow ? (
<AutoSizer>{renderList}</AutoSizer>
) : (
<StyledGridWrapper>{children}</StyledGridWrapper>
)}
</StyledTileContainer>
);
}
}
TileContainer.propTypes = {
itemHeight: PropTypes.number,
manualHeight: PropTypes.string,
children: PropTypes.any.isRequired,
useReactWindow: PropTypes.bool,
className: PropTypes.string,
id: PropTypes.string,
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
};
TileContainer.defaultProps = {
itemHeight: 50,
useReactWindow: true,
id: "rowContainer",
};
export default withTranslation(["Home", "Common"])(TileContainer);

View File

@ -0,0 +1,37 @@
import React from "react";
import PropTypes from "prop-types";
import {
StyledTileContent,
MainContainerWrapper,
MainContainer,
} from "../StyledTileView";
const TileContent = (props) => {
const { children, id, className, style, onClick } = props;
return (
<StyledTileContent
id={id}
className={className}
style={style}
onClick={onClick}
>
<MainContainerWrapper
mainContainerWidth={children.props && children.props.containerWidth}
>
<MainContainer className="row-main-container">{children}</MainContainer>
</MainContainerWrapper>
</StyledTileContent>
);
};
TileContent.propTypes = {
children: PropTypes.node.isRequired,
className: PropTypes.string,
id: PropTypes.string,
onClick: PropTypes.func,
sideColor: PropTypes.string,
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
};
export default TileContent;

View File

@ -0,0 +1,31 @@
import React from "react";
import Section from "@appserver/common/components/Section";
import SectionHeaderContent from "./Header";
import SectionBodyContent from "./Body";
import { InfoPanelBodyContent } from "../Home/InfoPanel";
import InfoPanelHeaderContent from "../Home/InfoPanel/GalleryHeader";
const FormGallery = () => {
return (
<Section
// withBodyScroll
// withBodyAutoFocus={!isMobile}
>
<Section.SectionHeader>
<SectionHeaderContent />
</Section.SectionHeader>
<Section.SectionBody>
<SectionBodyContent />
</Section.SectionBody>
<Section.InfoPanelHeader>
<InfoPanelHeaderContent />
</Section.InfoPanelHeader>
<Section.InfoPanelBody>
<InfoPanelBodyContent isGallery />
</Section.InfoPanelBody>
</Section>
);
};
export default FormGallery;

View File

@ -0,0 +1,26 @@
import React from "react";
import styled from "styled-components";
import Text from "@appserver/components/text";
import { withTranslation } from "react-i18next";
const StyledGalleryEmptyScreen = styled.div`
.info-panel_gallery-empty-screen-img {
display: block;
margin: 0 auto;
padding: 56px 0 48px 0;
}
`;
const GalleryEmptyScreen = ({ t }) => {
return (
<StyledGalleryEmptyScreen className="info-panel_gallery-empty-screen">
<img
className="info-panel_gallery-empty-screen-img"
src="images/form-gallery-search.react.svg"
alt="Empty Screen Gallery image"
/>
<Text textAlign="center">{t("GalleryEmptyScreenDescription")}</Text>
</StyledGalleryEmptyScreen>
);
};
export default withTranslation("FormGallery")(GalleryEmptyScreen);

View File

@ -0,0 +1,83 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { withTranslation } from "react-i18next";
import { LANGUAGE } from "@appserver/common/constants";
import Text from "@appserver/components/text";
import { ReactSVG } from "react-svg";
import {
StyledProperties,
StyledSubtitle,
StyledGalleryThumbnail,
StyledTitle,
} from "./styles/styles.js";
import moment from "moment";
const SingleItem = (props) => {
const { t, selectedItem, getIcon } = props;
const parseAndFormatDate = (date) => {
return moment(date)
.locale(localStorage.getItem(LANGUAGE))
.format("DD.MM.YY hh:mm A");
};
const src = getIcon(32, ".docxf");
const thumbnailBlank = getIcon(96, ".docxf");
const thumbnailUrl = selectedItem.attributes.card_prewiew.data.attributes.url;
//console.log("item", selectedItem);
return (
<>
<StyledTitle>
<ReactSVG className="icon" src={src} />
<Text className="text">{selectedItem.attributes.name_form}</Text>
</StyledTitle>
{thumbnailUrl ? (
<StyledGalleryThumbnail>
<img className="info-panel_gallery-img" src={thumbnailUrl} alt="" />
</StyledGalleryThumbnail>
) : (
<div className="no-thumbnail-img-wrapper">
<ReactSVG className="no-thumbnail-img" src={thumbnailBlank} />
</div>
)}
<StyledSubtitle>
<Text fontWeight="600" fontSize="14px">
{t("SystemProperties")}
</Text>
</StyledSubtitle>
<StyledProperties>
<div className="property">
<Text className="property-title">{t("Home:ByLastModifiedDate")}</Text>
<Text className="property-content">
{parseAndFormatDate(selectedItem.updatedAt)}
</Text>
</div>
<div className="property">
<Text className="property-title">{t("Common:Size")}</Text>
<Text className="property-content">
{selectedItem.attributes.file_size}
</Text>
</div>
<div className="property">
<Text className="property-title">{t("Common:Pages")}</Text>
<Text className="property-content">
{selectedItem.attributes.file_pages}
</Text>
</div>
</StyledProperties>
</>
);
};
export default inject(({ settingsStore }) => {
const { getIcon } = settingsStore;
return {
getIcon,
};
})(withTranslation(["InfoPanel", "Common", "Home"])(observer(SingleItem)));

View File

@ -1,9 +1,11 @@
import { inject, observer } from "mobx-react";
import React, { useEffect, useState } from "react";
import React from "react";
import { withTranslation } from "react-i18next";
import { withRouter } from "react-router";
import SeveralItems from "./SeveralItems";
import SingleItem from "./SingleItem";
import GalleryItem from "./GalleryItem";
import GalleryEmptyScreen from "./GalleryEmptyScreen";
import { StyledInfoRoomBody } from "./styles/styles.js";
import { Base } from "@appserver/components/themes";
@ -20,6 +22,8 @@ const InfoPanelBodyContent = ({
isRecycleBinFolder,
isRecentFolder,
isFavoritesFolder,
isGallery,
gallerySelected,
}) => {
const singleItem = (item) => {
const dontShowLocation = item.isFolder && item.parentId === 0;
@ -47,7 +51,15 @@ const InfoPanelBodyContent = ({
);
};
return (
return isGallery ? (
!gallerySelected ? (
<GalleryEmptyScreen />
) : (
<StyledInfoRoomBody>
<GalleryItem selectedItem={gallerySelected} />
</StyledInfoRoomBody>
)
) : (
<StyledInfoRoomBody>
<>
{selectedItems.length === 0 ? (
@ -76,7 +88,12 @@ export default inject(
treeFoldersStore,
selectedFolderStore,
}) => {
const { selection, getFolderInfo, getShareUsers } = filesStore;
const {
selection,
getFolderInfo,
getShareUsers,
gallerySelected,
} = filesStore;
const { getIcon, getFolderIcon } = settingsStore;
const { onSelectItem } = filesActionsStore;
const { setSharingPanelVisible } = dialogsStore;
@ -98,6 +115,7 @@ export default inject(
isRecycleBinFolder,
isRecentFolder,
isFavoritesFolder,
gallerySelected,
};
}
)(

View File

@ -64,6 +64,16 @@ const StyledTitle = styled.div`
}
`;
const StyledGalleryThumbnail = styled.div`
max-height: 200px;
overflow: hidden;
.info-panel_gallery-img {
display: block;
margin: 0 auto;
}
`;
const StyledThumbnail = styled.div`
display: flex;
justify-content: center;
@ -231,4 +241,5 @@ export {
StyledAccess,
StyledAccessItem,
StyledOpenSharingPanel,
StyledGalleryThumbnail,
};

View File

@ -0,0 +1,8 @@
import React from "react";
import { withTranslation } from "react-i18next";
const InfoPanelHeaderContent = ({ t }) => {
return <>{t("FormTemplateInfo")}</>;
};
export default withTranslation(["FormGallery"])(InfoPanelHeaderContent);

View File

@ -32,6 +32,7 @@ import DragTooltip from "../../components/DragTooltip";
import { observer, inject } from "mobx-react";
import config from "../../../package.json";
import { Consumer } from "@appserver/components/utils/context";
import { FileAction } from "@appserver/common/constants";
class PureHome extends React.Component {
componentDidMount() {
@ -48,6 +49,9 @@ class PureHome extends React.Component {
getFileInfo,
setIsPrevSettingsModule,
isPrevSettingsModule,
gallerySelected,
setAction,
setIsUpdatingRowItem,
} = this.props;
if (!window.location.href.includes("#preview")) {
@ -158,11 +162,27 @@ class PureHome extends React.Component {
const folderId = filter.folder;
//console.log("filter", filter);
return fetchFiles(folderId, filter).then((data) => {
const pathParts = data.selectedFolder.pathParts;
const newExpandedKeys = createTreeFolders(pathParts, expandedKeys);
setExpandedKeys(newExpandedKeys);
});
return fetchFiles(folderId, filter)
.then((data) => {
const pathParts = data.selectedFolder.pathParts;
const newExpandedKeys = createTreeFolders(
pathParts,
expandedKeys
);
setExpandedKeys(newExpandedKeys);
})
.then(() => {
if (gallerySelected) {
setIsUpdatingRowItem(false);
setAction({
type: FileAction.Create,
extension: "docxf",
fromTemplate: true,
title: gallerySelected.attributes.name_form,
id: -1,
});
}
});
}
return Promise.resolve();
@ -394,9 +414,11 @@ export default inject(
getFileInfo,
setIsPrevSettingsModule,
isPrevSettingsModule,
gallerySelected,
setIsUpdatingRowItem,
} = filesStore;
const { id } = fileActionStore;
const { id, setAction } = fileActionStore;
const {
isRecycleBinFolder,
isPrivacyFolder,
@ -490,6 +512,9 @@ export default inject(
setIsPrevSettingsModule,
isPrevSettingsModule,
gallerySelected,
setAction,
setIsUpdatingRowItem,
};
}
)(withRouter(observer(Home)));

View File

@ -1,4 +1,4 @@
import { makeObservable, action, observable } from "mobx";
import { makeAutoObservable } from "mobx";
class FileActionStore {
id = null;
@ -6,20 +6,15 @@ class FileActionStore {
extension = null;
title = "";
templateId = null;
fromTemplate = null;
constructor() {
makeObservable(this, {
type: observable,
extension: observable,
id: observable,
title: observable,
templateId: observable,
setAction: action,
});
makeAutoObservable(this);
}
setAction = (fileAction) => {
if (fileAction.fromTemplate === undefined && this.fromTemplate) return;
const fileActionItems = Object.keys(fileAction);
for (let key of fileActionItems) {
if (key in this) {

View File

@ -392,6 +392,7 @@ class FilesActionStore {
extension: null,
title: "",
templateId: null,
fromTemplate: null,
});
setIsLoading(false);
type === FileAction.Rename &&

View File

@ -64,6 +64,8 @@ class FilesStore {
isPrevSettingsModule = false;
enabledHotkeys = true;
oformFiles = null;
gallerySelected = null;
constructor(
authStore,
@ -278,10 +280,27 @@ class FilesStore {
}
}
requests.push(getFilesSettings());
requests.push(this.getOforms());
return Promise.all(requests).then(() => (this.isInit = true));
};
getOforms = async () => {
const oformData = await this.authStore.getOforms();
runInAction(() => {
this.oformFiles = oformData?.data?.data ? oformData.data.data : [];
});
};
get hasGalleryFiles() {
return this.oformFiles && !!this.oformFiles.length;
}
setGallerySelected = (gallerySelected) => {
this.gallerySelected = gallerySelected;
};
setFirstLoad = (firstLoad) => {
this.firstLoad = firstLoad;
};
@ -1171,10 +1190,12 @@ class FilesStore {
return api.files.addFileToRecentlyViewed(fileId);
};
createFile = (folderId, title, templateId) => {
return api.files.createFile(folderId, title, templateId).then((file) => {
return Promise.resolve(file);
});
createFile = (folderId, title, templateId, formId) => {
return api.files
.createFile(folderId, title, templateId, formId)
.then((file) => {
return Promise.resolve(file);
});
};
createFolder(parentFolderId, title) {

View File

@ -15,6 +15,10 @@ class InfoPanelStore {
this.isVisible = !this.isVisible;
};
setInfoPanelIsVisible = (isVisible) => {
this.isVisible = isVisible;
};
setVisible = () => {
this.isVisible = true;
};

File diff suppressed because it is too large Load Diff

View File

@ -52,6 +52,7 @@ using ASC.Files.Core.Model;
using ASC.Files.Core.Resources;
using ASC.Files.Core.Security;
using ASC.Files.Core.Services.NotifyService;
using ASC.Files.Core.Services.OFormService;
using ASC.MessagingSystem;
using ASC.Web.Core.Files;
using ASC.Web.Core.PublicResources;
@ -80,6 +81,8 @@ namespace ASC.Web.Files.Services.WCFService
public class FileStorageService<T> //: IFileStorageService
{
private static readonly FileEntrySerializer serializer = new FileEntrySerializer();
private readonly OFormRequestManager _oFormRequestManager;
private Global Global { get; }
private GlobalStore GlobalStore { get; }
private GlobalFolderHelper GlobalFolderHelper { get; }
@ -167,7 +170,8 @@ namespace ASC.Web.Files.Services.WCFService
FileTrackerHelper fileTracker,
ICacheNotify<ThumbnailRequest> thumbnailNotify,
EntryStatusManager entryStatusManager,
CompressToArchive compressToArchive)
CompressToArchive compressToArchive,
OFormRequestManager oFormRequestManager)
{
Global = global;
GlobalStore = globalStore;
@ -212,6 +216,7 @@ namespace ASC.Web.Files.Services.WCFService
ThumbnailNotify = thumbnailNotify;
EntryStatusManager = entryStatusManager;
CompressToArchive = compressToArchive;
_oFormRequestManager = oFormRequestManager;
}
public async Task<Folder<T>> GetFolderAsync(T folderId)
@ -625,7 +630,15 @@ namespace ASC.Web.Files.Services.WCFService
file.Title = FileUtility.ReplaceFileExtension(title, fileExt);
}
if (EqualityComparer<TTemplate>.Default.Equals(fileWrapper.TemplateId, default(TTemplate)))
if (fileWrapper.FormId != 0)
{
using (var stream = await _oFormRequestManager.Get(fileWrapper.FormId))
{
file.ContentLength = stream.Length;
file = await fileDao.SaveFileAsync(file, stream);
}
}
else if (EqualityComparer<TTemplate>.Default.Equals(fileWrapper.TemplateId, default(TTemplate)))
{
var culture = UserManager.GetUsers(AuthContext.CurrentAccount.ID).GetCulture();
var storeTemplate = GetStoreTemplate();
@ -2530,6 +2543,7 @@ namespace ASC.Web.Files.Services.WCFService
{
public T ParentId { get; set; }
public string Title { get; set; }
public TTempate TemplateId { get; set; }
public TTempate TemplateId { get; set; }
public int FormId { get; set; }
}
}

View File

@ -7,5 +7,7 @@
public T TemplateId { get; set; }
public bool EnableExternalExt { get; set; }
public int FormId { get; set; }
}
}

View File

@ -0,0 +1,54 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace ASC.Files.Core.Services.OFormService;
public class OFormDataAttributes
{
[JsonPropertyName("file_oform")]
public OFromFile File { get; set; }
}
public class OFromFile
{
public IEnumerable<OFromFileData> Data { get; set; }
}
public class OFromFileData
{
public int Id { get; set; }
public OFromFileAttribute Attributes { get; set; }
}
public class OFromFileAttribute
{
public string Name { get; set; }
public string Url { get; set; }
public string Ext { get; set; }
}

View File

@ -0,0 +1,128 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using ASC.Common;
using ASC.Common.Utils;
namespace ASC.Files.Core.Services.OFormService;
[Singletone]
public class OFormRequestManager
{
private readonly OFormSettings _configuration;
private readonly IHttpClientFactory _httpClientFactory;
private readonly TempPath _tempPath;
private readonly SemaphoreSlim _semaphoreSlim;
private OFromRequestData _data;
public OFormRequestManager(
ConfigurationExtension configuration,
IHttpClientFactory httpClientFactory,
TempPath tempPath)
{
_semaphoreSlim = new SemaphoreSlim(1);
_configuration = configuration.GetSetting<OFormSettings>("files:oform");
_httpClientFactory = httpClientFactory;
_tempPath = tempPath;
}
public async Task Init(CancellationToken cancellationToken)
{
await _semaphoreSlim.WaitAsync();
try
{
using var httpClient = _httpClientFactory.CreateClient();
using var response = await httpClient.GetAsync(_configuration.Url);
if (response.StatusCode != HttpStatusCode.OK)
{
return;
}
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
using var combined = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cts.Token);
_data = JsonSerializer.Deserialize<OFromRequestData>(await response.Content.ReadAsStringAsync(combined.Token), options);
}
finally
{
_semaphoreSlim.Release();
}
}
public async Task<FileStream> Get(int id)
{
await _semaphoreSlim.WaitAsync(TimeSpan.FromSeconds(_configuration.Period));
try
{
if (_data == null) throw new Exception("not found");
var item = _data.Data.FirstOrDefault(r => r.Id == id);
if (item == null) throw new Exception("not found");
var file = item.Attributes.File.Data.FirstOrDefault(f => f.Attributes.Ext == _configuration.Ext);
if (file == null) throw new Exception("not found");
var filePath = Path.Combine(_tempPath.GetTempPath(), file.Attributes.Name);
if (!File.Exists(filePath))
{
await DownloadAndSave(file, filePath);
}
return File.OpenRead(filePath);
}
finally
{
_semaphoreSlim.Release();
}
}
private async Task DownloadAndSave(OFromFileData fileData, string filePath)
{
using var httpClient = _httpClientFactory.CreateClient();
using var response = await httpClient.GetAsync(fileData.Attributes.Url);
using var stream = await response.Content.ReadAsStreamAsync();
using var fileStream = new FileStream(filePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read);
await stream.CopyToAsync(fileStream);
}
}

View File

@ -0,0 +1,58 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using System;
using System.Threading;
using System.Threading.Tasks;
using ASC.Common;
using ASC.Common.Utils;
using Microsoft.Extensions.Hosting;
namespace ASC.Files.Core.Services.OFormService;
[Singletone]
public sealed class OFormService : BackgroundService
{
private readonly TimeSpan _formPeriod;
private readonly OFormRequestManager _oFormRequestManager;
public OFormService(OFormRequestManager oFormRequestManager, ConfigurationExtension configurationExtension)
{
_oFormRequestManager = oFormRequestManager;
_formPeriod = TimeSpan.FromSeconds(configurationExtension.GetSetting<OFormSettings>("files:oform").Period);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await _oFormRequestManager.Init(stoppingToken);
await Task.Delay(_formPeriod, stoppingToken);
}
}
}

View File

@ -0,0 +1,34 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Files.Core.Services.OFormService;
public class OFormSettings
{
public string Url { get; set; }
public int Period { get; set; }
public string Ext { get; set; }
}

View File

@ -0,0 +1,33 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Files.Core.Services.OFormService;
public class OFromData
{
public int Id { get; set; }
public OFormDataAttributes Attributes { get; set; }
}

View File

@ -0,0 +1,34 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using System.Collections.Generic;
namespace ASC.Files.Core.Services.OFormService;
public class OFromRequestData
{
public IEnumerable<OFromData> Data { get; set; }
}

View File

@ -975,14 +975,14 @@ namespace ASC.Api.Documents
[Create("@my/file")]
public Task<FileWrapper<int>> CreateFileFromBodyAsync([FromBody] CreateFileModel<JsonElement> model)
{
return FilesControllerHelperInt.CreateFileAsync(GlobalFolderHelper.FolderMy, model.Title, model.TemplateId, model.EnableExternalExt);
return FilesControllerHelperInt.CreateFileAsync(GlobalFolderHelper.FolderMy, model.Title, model.TemplateId, model.FormId, model.EnableExternalExt);
}
[Create("@my/file")]
[Consumes("application/x-www-form-urlencoded")]
public Task<FileWrapper<int>> CreateFileFromFormAsync([FromForm] CreateFileModel<JsonElement> model)
{
return FilesControllerHelperInt.CreateFileAsync(GlobalFolderHelper.FolderMy, model.Title, model.TemplateId, model.EnableExternalExt);
return FilesControllerHelperInt.CreateFileAsync(GlobalFolderHelper.FolderMy, model.Title, model.TemplateId, model.FormId, model.EnableExternalExt);
}
/// <summary>
@ -997,27 +997,27 @@ namespace ASC.Api.Documents
[Create("{folderId}/file")]
public Task<FileWrapper<string>> CreateFileFromBodyAsync(string folderId, [FromBody] CreateFileModel<JsonElement> model)
{
return FilesControllerHelperString.CreateFileAsync(folderId, model.Title, model.TemplateId, model.EnableExternalExt);
return FilesControllerHelperString.CreateFileAsync(folderId, model.Title, model.TemplateId, model.FormId, model.EnableExternalExt);
}
[Create("{folderId}/file")]
[Consumes("application/x-www-form-urlencoded")]
public Task<FileWrapper<string>> CreateFileFromFormAsync(string folderId, [FromForm] CreateFileModel<JsonElement> model)
{
return FilesControllerHelperString.CreateFileAsync(folderId, model.Title, model.TemplateId, model.EnableExternalExt);
return FilesControllerHelperString.CreateFileAsync(folderId, model.Title, model.TemplateId, model.FormId, model.EnableExternalExt);
}
[Create("{folderId:int}/file")]
public Task<FileWrapper<int>> CreateFileFromBodyAsync(int folderId, [FromBody] CreateFileModel<JsonElement> model)
{
return FilesControllerHelperInt.CreateFileAsync(folderId, model.Title, model.TemplateId, model.EnableExternalExt);
return FilesControllerHelperInt.CreateFileAsync(folderId, model.Title, model.TemplateId, model.FormId, model.EnableExternalExt);
}
[Create("{folderId:int}/file")]
[Consumes("application/x-www-form-urlencoded")]
public Task<FileWrapper<int>> CreateFileFromFormAsync(int folderId, [FromForm] CreateFileModel<JsonElement> model)
{
return FilesControllerHelperInt.CreateFileAsync(folderId, model.Title, model.TemplateId, model.EnableExternalExt);
return FilesControllerHelperInt.CreateFileAsync(folderId, model.Title, model.TemplateId, model.FormId, model.EnableExternalExt);
}
/// <summary>

View File

@ -349,7 +349,7 @@ namespace ASC.Files.Helpers
return await FolderWrapperHelper.GetAsync(folder);
}
public async Task<FileWrapper<T>> CreateFileAsync(T folderId, string title, JsonElement templateId, bool enableExternalExt = false)
public async Task<FileWrapper<T>> CreateFileAsync(T folderId, string title, JsonElement templateId, int formId, bool enableExternalExt = false)
{
File<T> file;
@ -363,7 +363,7 @@ namespace ASC.Files.Helpers
}
else
{
file = await FileStorageService.CreateNewFileAsync(new FileModel<T, int> { ParentId = folderId, Title = title, TemplateId = 0 }, enableExternalExt);
file = await FileStorageService.CreateNewFileAsync(new FileModel<T, int> { ParentId = folderId, Title = title, TemplateId = 0, FormId = formId }, enableExternalExt);
}
return await FileWrapperHelper.GetAsync(file);

View File

@ -4,6 +4,7 @@ using System.Text.Json.Serialization;
using ASC.Api.Core;
using ASC.Api.Documents;
using ASC.Files.Core.Security;
using ASC.Files.Core.Services.OFormService;
using ASC.Web.Files;
using ASC.Web.Files.HttpHandlers;
using ASC.Web.Studio.Core.Notify;
@ -40,7 +41,9 @@ namespace ASC.Files
DIHelper.TryAdd<ChunkedUploaderHandlerService>();
DIHelper.TryAdd<DocuSignHandlerService>();
DIHelper.TryAdd<ThirdPartyAppHandlerService>();
DIHelper.TryAdd<OFormService>();
services.AddHostedService<OFormService>();
NotifyConfigurationExtension.Register(DIHelper);
}

View File

@ -31,7 +31,7 @@ const InfoItem = styled.div`
font-size: 13px;
line-height: 24px;
display: flex;
width: 360px;
width: 358px;
`;
const InfoItemLabel = styled.div`

View File

@ -158,5 +158,7 @@
"View": "View",
"ViewWeb": "View web version",
"Warning": "Warning",
"Connect": "Connect"
"Connect": "Connect",
"OFORMsGallery": "OFORMs gallery",
"Pages": "Pages"
}

View File

@ -158,5 +158,6 @@
"View": "Просмотр",
"ViewWeb": "Просмотреть веб-версию",
"Warning": "Внимание",
"Connect": "Подключить"
}
"Connect": "Подключить",
"OFORMsGallery": "Галерея OFORMs",
"Pages": "Страницы"}

View File

@ -15,10 +15,9 @@ const StyledMain = styled.main`
flex-direction: row;
box-sizing: border-box;
${isMobile &&
css`
height: calc(100vh - 48px);
`}
#article-container {
height: calc(100%) !important;
}
${isMobileOnly &&
css`
@ -30,7 +29,7 @@ const StyledMain = styled.main`
const Main = React.memo((props) => {
if (isIOS && !isFirefox) {
const vh = (window.innerHeight - 57) * 0.01;
const vh = (window.innerHeight - 48) * 0.01;
document.documentElement.style.setProperty("--vh", `${vh}px`);
}
//console.log("Main render");

View File

@ -44,7 +44,7 @@ const PortalRenaming = (props) => {
portalNameFromSessionStorage || tenantAlias
);
const [portalNameDefault, setPortalNameDefault] = useState(
portalNameDefaultFromSessionStorage || portalName
portalNameDefaultFromSessionStorage || tenantAlias
);
const [showReminder, setShowReminder] = useState(false);
@ -63,8 +63,7 @@ const PortalRenaming = (props) => {
const checkScroll = checkScrollSettingsBlock();
window.addEventListener("resize", checkInnerWidth);
window.addEventListener("resize", checkScroll);
window.addEventListener("resize", checkInnerWidth, checkScroll);
const scrollPortalName = checkScroll();
@ -91,7 +90,10 @@ const PortalRenaming = (props) => {
useEffect(() => {
if (isLoadedSetting) setIsLoadedPortalRenaming(isLoadedSetting);
}, [isLoadedSetting]);
if (portalNameDefault) {
checkChanges();
}
}, [isLoadedSetting, portalNameDefault]);
const onSavePortalRename = () => {
setIsLoadingPortalNameSave(true);
@ -124,9 +126,6 @@ const PortalRenaming = (props) => {
saveToSessionStorage("portalName", "");
setShowReminder(false);
}
//TODO: delete?
checkChanges();
};
const onValidateInput = (value) => {
@ -177,10 +176,8 @@ const PortalRenaming = (props) => {
if (settingIsEqualInitialValue(value)) {
saveToSessionStorage("portalName", "");
saveToSessionStorage("portalNameDefault", "");
setShowReminder(false);
} else {
saveToSessionStorage("portalName", value);
setShowReminder(true);
}
checkChanges();

View File

@ -61,11 +61,16 @@ class WelcomePageSettings extends React.Component {
componentDidMount() {
const { isLoaded, setIsLoadedWelcomePageSettings, tReady } = this.props;
const { greetingTitleDefault } = this.state;
window.addEventListener("resize", this.checkInnerWidth);
const isLoadedSetting = isLoaded && tReady;
if (isLoadedSetting) setIsLoadedWelcomePageSettings(isLoadedSetting);
if (greetingTitleDefault) {
this.checkChanges();
}
}
componentDidUpdate(prevProps, prevState) {