Web: Init Mail/Calendar Comming Soon pages

This commit is contained in:
Alexey Safronov 2021-04-12 13:41:16 +03:00
parent 4d644aff17
commit 6fbb566c4e
57 changed files with 2373 additions and 2 deletions

View File

@ -0,0 +1,2 @@
echo "RUN ASC.Web.Calendar"
call set BROWSER=none&&npm start --prefix ../../products/ASC.Calendar/Client

2
build/run/MailClient.bat Normal file
View File

@ -0,0 +1,2 @@
echo "RUN ASC.Web.Mail"
call set BROWSER=none&&npm start --prefix ../../products/ASC.Mail/Client

View File

@ -28,6 +28,14 @@
"name": "🚀 @appserver/projects",
"path": "products\\ASC.Projects\\Client"
},
{
"name": "🚀 @appserver/mail",
"path": "products\\ASC.Mail\\Client"
},
{
"name": "🚀 @appserver/calendar",
"path": "products\\ASC.Calendar\\Client"
},
{
"name": "🚀 @appserver/studio",
"path": "web\\ASC.Web.Client"

View File

@ -6,8 +6,13 @@
"packages/asc-web-common",
"web/ASC.Web.Login",
"web/ASC.Web.Client",
"web/ASC.Web.Editor",
"products/ASC.People/Client",
"products/ASC.Files/Client"
"products/ASC.Files/Client",
"products/ASC.CRM/Client",
"products/ASC.Projects/Client",
"products/ASC.Mail/Client",
"products/ASC.Calendar/Client"
],
"useWorkspaces": true
}

View File

@ -10,7 +10,9 @@
"products/ASC.People/Client",
"products/ASC.Files/Client",
"products/ASC.CRM/Client",
"products/ASC.Projects/Client"
"products/ASC.Projects/Client",
"products/ASC.Mail/Client",
"products/ASC.Calendar/Client"
],
"scripts": {
"wipe": "rimraf node_modules yarn.lock web/**/node_modules products/**/node_modules",

View File

@ -0,0 +1,7 @@
{
"presets": ["@babel/preset-react", "@babel/preset-env"],
"plugins": [
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-class-properties"
]
}

View File

@ -0,0 +1,3 @@
PUBLIC_URL=/products/calendar
WDS_SOCKET_PATH=/products/calendar/sockjs-node
PORT=5017

21
products/ASC.Calendar/Client/.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -0,0 +1,15 @@
FROM node:12
WORKDIR /usr/src/app
COPY package.json ./
COPY yarn.lock ./
RUN yarn install
COPY . .
RUN yarn build
EXPOSE 5017
CMD [ "yarn", "build:start" ]

View File

@ -0,0 +1,17 @@
// script to enable webpack-bundle-analyzer
process.env.NODE_ENV = "production";
const webpack = require("webpack");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
const webpackConfigProd = require("react-scripts/config/webpack.config")(
"production"
);
webpackConfigProd.plugins.push(new BundleAnalyzerPlugin());
// actually running compilation and waiting for plugin to start explorer
webpack(webpackConfigProd, (err, stats) => {
if (err || stats.hasErrors()) {
console.error(err);
}
});

View File

@ -0,0 +1,83 @@
{
"name": "@appserver/calendar",
"version": "0.1.3",
"private": "true",
"homepage": "/products/calendar",
"id": "32d24cb5-7ece-4606-9c94-19216ba42086",
"title": "ONLYOFFICE",
"scripts": {
"start": "webpack-cli serve",
"start-prod": "webpack --mode production && serve dist -p 5017",
"build": "webpack --mode production",
"serve": "serve dist -p 5017",
"clean": "rm -rf dist"
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-export-default-from": "^7.12.1",
"@babel/plugin-transform-runtime": "^7.12.1",
"@babel/preset-env": "^7.12.7",
"@babel/preset-react": "^7.12.10",
"@svgr/webpack": "^5.5.0",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^7.0.0",
"css-loader": "^3.6.0",
"html-webpack-plugin": "4.5.0",
"json-loader": "^0.5.7",
"serve": "11.3.2",
"source-map-loader": "^1.1.2",
"style-loader": "1.2.1",
"webpack": "5.14.0",
"webpack-cli": "4.5.0",
"webpack-dev-server": "3.11.2",
"workbox-webpack-plugin": "^6.1.1"
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"attr-accept": "^2.2.2",
"axios": "^0.21.0",
"email-addresses": "^3.1.0",
"i18next": "^19.8.4",
"i18next-http-backend": "^1.1.0",
"mobx": "^6.1.1",
"mobx-react": "^7.1.0",
"moment": "^2.29.1",
"copy-to-clipboard": "^3.2.0",
"fast-deep-equal": "^3.1.3",
"prop-types": "^15.7.2",
"rc-tree": "^2.1.4",
"re-resizable": "^6.9.0",
"react": "^17.0.1",
"react-autosize-textarea": "^7.1.0",
"react-content-loader": "^5.1.4",
"react-custom-scrollbars": "^4.2.1",
"react-device-detect": "^1.14.0",
"react-dom": "^17.0.1",
"react-dropzone": "^11.2.4",
"react-i18next": "^11.7.3",
"react-hammerjs": "^1.0.1",
"react-onclickoutside": "^6.9.0",
"react-player": "^1.15.3",
"react-resize-detector": "^5.2.0",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-string-format": "^0.1.0",
"react-svg": "^12.0.0",
"react-text-mask": "^5.4.3",
"react-toastify": "^6.1.0",
"react-tooltip": "^4.2.11",
"react-viewer": "^3.2.2",
"react-virtualized-auto-sizer": "^1.0.2",
"react-window": "^1.8.6",
"react-window-infinite-loader": "^1.0.5",
"resize-image": "^0.1.0",
"sass": "^1.29.0",
"sass-loader": "^10.1.0",
"sjcl": "^1.0.8",
"screenfull": "^5.1.0",
"styled-components": "^5.2.1",
"workbox-window": "^6.1.1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.8814 3H18.9943V1.02931C18.9943 0.460813 18.5335 0 17.9649 0H17.0301C16.4616 0 16.001 0.460813 16.001 1.02931V3H7.99588V1.02931C7.99588 0.460813 7.53507 0 6.96674 0H6.02914C5.46064 0 5 0.460813 5 1.02931V3H3.11844C1.95051 3 1 3.95017 1 5.11844V20.8965C1 22.0641 1.95051 23.0149 3.11844 23.0149H20.8816C22.0493 23.0149 23 22.0642 23 20.8965V5.11844C22.9998 3.95017 22.0491 3 20.8814 3ZM21 21.001H3.02614V8.00104H21V21.001ZM9.8844 17.6579C10.3376 18.114 11.0732 18.114 11.5266 17.6579L16.66 12.4939C16.8777 12.2748 17 11.9777 17 11.668C17 11.3583 16.8777 11.0611 16.66 10.8421C16.2066 10.386 15.4709 10.386 15.0178 10.8421L10.913 14.9713C10.7984 15.0862 10.6126 15.0862 10.4982 14.9713L8.98223 13.4466C8.52885 12.9905 7.7932 12.9905 7.34004 13.4466C6.88665 13.9024 6.88665 14.6425 7.34004 15.0984L9.8844 17.6579Z" fill="#7A95B0"/>
</svg>

After

Width:  |  Height:  |  Size: 984 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -0,0 +1,261 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="shortcut icon" href="favicon.ico" />
<link rel="manifest" href="manifest.json" />
<!-- Tell the browser it's a PWA -->
<meta name="mobile-web-app-capable" content="yes" />
<!-- Tell iOS it's a PWA -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<link rel="apple-touch-icon" href="appIcon.png" />
<link
href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i"
rel="stylesheet"
type="text/css"
/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<style type="text/css">
body {
height: 100%;
margin: 0;
padding: 0;
width: 100%;
font-family: "Open Sans", sans-serif;
font-size: 13px;
-webkit-font-smoothing: antialiased;
}
.temp-header-container {
align-items: center;
background-color: rgb(15, 64, 113);
display: grid;
grid-template-columns: 24px 168px 1fr 36px;
grid-template-rows: 1fr;
grid-column-gap: 16px;
width: calc(100vw - 16px);
height: 56px;
padding-right: 16px;
}
.temp-content-loader {
padding: 16px;
height: calc(100vh - 91px);
display: grid;
grid-template-columns: 250px 1fr;
grid-template-rows: calc(100vh - 91px);
grid-column-gap: 8px;
}
@media (max-width: 1024px) {
.temp-content-loader {
grid-template-columns: 1fr;
}
.temp-content__article {
display: none;
}
}
.burger-loader-svg {
width: 24px;
height: 24px;
padding-left: 16px;
}
.logo-loader-svg {
width: 168px;
height: 24px;
position: relative;
cursor: pointer;
padding-left: 16px;
}
.avatar-loader-svg {
width: 36px;
height: 36px;
}
</style>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript> You need to enable JavaScript to run this app. </noscript>
<div id="root"></div>
<div id="temp-content">
<header class="temp-header-container">
<div id="burger-loader-svg" class="burger-loader-svg">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria0"
preserveAspectRatio="none"
>
<rect
rx="3"
ry="3"
width="24"
height="24"
style="fill: url('#fill0');"
></rect>
<defs>
<linearGradient id="fill0">
<stop
offset="0.599964"
stop-color="#fff"
stop-opacity="0.25"
></stop>
</linearGradient>
</defs>
</svg>
</div>
<div id="logo-loader-svg" class="logo-loader-svg">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria01"
preserveAspectRatio="none"
>
<rect
rx="3"
ry="3"
width="168"
height="24"
style="fill: url('#fill01');"
></rect>
<defs>
<linearGradient id="fill01">
<stop
offset="0.599964"
stop-color="#fff"
stop-opacity="0.25"
></stop>
</linearGradient>
</defs>
</svg>
</div>
<div></div>
<div id="avatar-loader-svg" class="avatar-loader-svg">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria01"
preserveAspectRatio="none"
>
<circle
cx="18"
cy="18"
r="18"
width="36"
height="36"
style="fill: url('#fill02');"
></circle>
<defs>
<linearGradient id="fill02">
<stop
offset="0.599964"
stop-color="#fff"
stop-opacity="0.25"
></stop>
</linearGradient>
</defs>
</svg>
</div>
</header>
<div class="temp-content-loader">
<div class="temp-content__article">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria1"
preserveAspectRatio="none"
>
<rect
x="0"
y="0"
width="100%"
height="100%"
clip-path="url(#clip-path1)"
style="fill: url('#fill1');"
></rect>
<defs>
<clipPath id="clip-path1">
<rect x="3" y="3" rx="5" ry="5" width="100%" />
</clipPath>
<linearGradient id="fill1">
<stop
offset="0.599964"
stop-color="#000000"
stop-opacity="0.1"
></stop>
</linearGradient>
</defs>
</svg>
</div>
<div>
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria2"
preserveAspectRatio="none"
>
<rect
x="0"
y="0"
width="100%"
height="100%"
clip-path="url(#clip-path2)"
style="fill: url('#fill2');"
></rect>
<defs>
<clipPath id="clip-path2">
<rect x="3" y="3" rx="5" ry="5" width="100%" />
</clipPath>
<linearGradient id="fill2">
<stop
offset="0.599964"
stop-color="#000000"
stop-opacity="0.1"
></stop>
</linearGradient>
</defs>
</svg>
</div>
</div>
</div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script>
console.log("It's Calendar INIT");
</script>
</body>
</html>

View File

@ -0,0 +1,6 @@
{
"ComingSoon": "Coming soon",
"ViewWeb": "View web version",
"OpenApp": "Open your {{title}} app",
"LearnMore": "Learn more"
}

View File

@ -0,0 +1,6 @@
{
"ComingSoon": "Скоро появится",
"ViewWeb": "Просмотреть веб-версию",
"OpenApp": "Откройте {{title}}",
"LearnMore": "Узнать больше"
}

View File

@ -0,0 +1,15 @@
{
"short_name": "ASC.Calendar",
"name": "ASC.Calendar",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,7 @@
//import "./wdyr";
import React from "react";
import Shell from "studio/shell";
const App = () => <Shell />;
export default App;

View File

@ -0,0 +1,15 @@
import React from "react";
import ReactDOM from "react-dom";
import { MemoryRouter } from "react-router-dom";
import App from "./App";
it("renders without crashing", async () => {
const div = document.createElement("div");
ReactDOM.render(
<MemoryRouter>
<App />
</MemoryRouter>,
div
);
await new Promise((resolve) => setTimeout(resolve, 1000));
});

View File

@ -0,0 +1,71 @@
import React, { useEffect } from "react";
import { Provider as PeopleProvider, inject, observer } from "mobx-react";
import { Switch } from "react-router-dom";
import CalendarStore from "./store/CalendarStore";
import ErrorBoundary from "@appserver/common/components/ErrorBoundary";
import toastr from "studio/toastr";
import PrivateRoute from "@appserver/common/components/PrivateRoute";
import AppLoader from "@appserver/common/components/AppLoader";
import { combineUrl, updateTempContent } from "@appserver/common/utils";
import config from "../package.json";
import "./custom.scss";
import i18n from "./i18n";
import { I18nextProvider } from "react-i18next";
import Home from "./pages/Home";
import { AppServerConfig } from "@appserver/common/constants";
const { proxyURL } = AppServerConfig;
const homepage = config.homepage;
const PROXY_HOMEPAGE_URL = combineUrl(proxyURL, homepage);
const Error404 = React.lazy(() => import("studio/Error404"));
const Error404Route = (props) => (
<React.Suspense fallback={<AppLoader />}>
<ErrorBoundary>
<Error404 {...props} />
</ErrorBoundary>
</React.Suspense>
);
const CalendarContent = (props) => {
const { isLoaded, loadBaseInfo } = props;
useEffect(() => {
loadBaseInfo()
.catch((err) => toastr.error(err))
.finally(() => {
//this.props.setIsLoaded(true);
updateTempContent();
});
}, []);
useEffect(() => {
if (isLoaded) updateTempContent();
}, [isLoaded]);
return (
<Switch>
<PrivateRoute exact path={PROXY_HOMEPAGE_URL} component={Home} />
<PrivateRoute component={Error404Route} />
</Switch>
);
};
const Calendar = inject(({ auth, calendarStore }) => ({
loadBaseInfo: async () => {
await calendarStore.init();
auth.setProductVersion(config.version);
},
isLoaded: auth.isLoaded && calendarStore.isLoaded,
}))(observer(CalendarContent));
const calendarStore = new CalendarStore();
export default (props) => (
<PeopleProvider calendarStore={calendarStore}>
<I18nextProvider i18n={i18n}>
<Calendar {...props} />
</I18nextProvider>
</PeopleProvider>
);

View File

@ -0,0 +1,9 @@
import App from "./App";
import React from "react";
import ReactDOM from "react-dom";
import config from "../package.json";
import { registerSW } from "@appserver/common/utils/sw-helper";
ReactDOM.render(<App />, document.getElementById("root"));
registerSW(config.homepage);

View File

@ -0,0 +1,27 @@
// Override default variables before the import
$font-family-base: "Open Sans", sans-serif;
html,
body {
height: 100%;
}
#root {
min-height: 100%;
position: relative;
.pageLoader {
position: absolute;
left: calc(50% - 20px);
top: 35%;
}
}
body {
margin: 0;
overflow: hidden;
}
body.loading * {
cursor: wait !important;
}

View File

@ -0,0 +1,26 @@
import authStore from "@appserver/common/store/AuthStore";
//import store from "../store/store";
//const { getCurrentProduct } = commonStore.auth.selectors;
export const setDocumentTitle = (subTitle = null) => {
// const state = store.getState();
// const { auth: commonState } = state;
const { isAuthenticated, settingsStore, product: currentModule } = authStore;
const { organizationName } = settingsStore;
let title;
if (subTitle) {
if (isAuthenticated && currentModule) {
title = subTitle + " - " + currentModule.title;
} else {
title = subTitle + " - " + organizationName;
}
} else if (currentModule && organizationName) {
title = currentModule.title + " - " + organizationName;
} else {
title = organizationName;
}
document.title = title;
};

View File

@ -0,0 +1,58 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend";
import config from "../package.json";
import { LANGUAGE } from "@appserver/common/constants";
//import LanguageDetector from "i18next-browser-languagedetector";
// not like to use this?
// have a look at the Quick start guide
// for passing in lng and translations on init
const languages = ["en", "ru"];
i18n
/*
load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
learn more: https://github.com/i18next/i18next-http-backend
*/
.use(Backend)
/*
detect user language
learn more: https://github.com/i18next/i18next-browser-languageDetector
*/
//.use(LanguageDetector)
/*
pass the i18n instance to react-i18next.
*/
.use(initReactI18next)
/*
init i18next
for all options read: https://www.i18next.com/overview/configuration-options
*/
.init({
lng: localStorage.getItem(LANGUAGE) || "en",
supportedLngs: languages,
whitelist: languages,
fallbackLng: "en",
load: "languageOnly",
//debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format) {
if (format === "lowercase") return value.toLowerCase();
return value;
},
},
backend: {
loadPath: `${config.homepage}/locales/{{lng}}/{{ns}}.json`,
},
react: {
useSuspense: false,
},
});
export default i18n;

View File

@ -0,0 +1 @@
import("./bootstrap");

View File

@ -0,0 +1,254 @@
import React, { useEffect } from "react";
import { ReactSVG } from "react-svg";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
import Text from "@appserver/components/text";
import Link from "@appserver/components/link";
import Badge from "@appserver/components/badge";
import Box from "@appserver/components/box";
import EmptyScreenContainer from "@appserver/components/empty-screen-container";
import ExternalLinkIcon from "../../../../../../public/images/external.link.react.svg";
import Loaders from "@appserver/common/components/Loaders";
import toastr from "studio/toastr";
import PageLayout from "@appserver/common/components/PageLayout";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { isMobile, isIOS } from "react-device-detect";
import { setDocumentTitle } from "../../helpers/utils";
import { inject } from "mobx-react";
import i18n from "../../i18n";
import { I18nextProvider } from "react-i18next";
import { combineUrl, deleteCookie } from "@appserver/common/utils";
const commonStyles = `
.link-box {
margin: 8px 0;
.view-web-link {
margin: 8px;
:focus {
outline: 0;
}
}
}
`;
const ComingSoonPage = styled.div`
padding: ${isMobile ? "62px 0 0 0" : "0"};
width: 336px;
margin: 0 auto;
.module-logo-icon {
float: left;
margin-top: 8px;
margin-right: 16px;
}
.module-title {
margin-top: 14px;
margin-bottom: 14px;
}
.module-info {
margin-bottom: 18px;
.learn-more-link {
white-space: nowrap;
}
}
.coming-soon-badge {
margin-bottom: 26px;
}
${commonStyles}
`;
const StyledDesktopContainer = styled(EmptyScreenContainer)`
img {
width: 150px;
height: 150px;
}
span:first-of-type {
font-size: 24px;
}
span {
font-size: 14px;
> p {
font-size: 14px;
.learn-more-link {
white-space: nowrap;
}
}
}
${commonStyles}
.view-web-link {
font-size: 14px;
}
.coming-soon-badge > div > p {
font-size: 13px;
}
`;
const ExternalLink = ({ label, href, onClick }) => (
<Box className="link-box">
<ExternalLinkIcon color="#333333" size={isMobile ? "small" : "medium"} />
<Link
as="a"
href={href}
onClick={onClick}
target="_blank"
className="view-web-link"
color="#555F65"
isBold
isHovered
>
{label}
</Link>
</Box>
);
const Body = ({ modules, match, isLoaded, setCurrentProductId }) => {
const { t } = useTranslation("ComingSoon");
const { error } = match.params;
const { pathname, protocol, hostname } = window.location;
const currentModule = modules.find((m) => m.link === pathname);
const {
id,
title,
description,
imageUrl,
link,
originUrl,
helpUrl,
} = currentModule;
const url = originUrl ? originUrl : link;
const webLink = combineUrl(
protocol + "//" + hostname,
`${url}?desktop_view=true`
);
const appLink = isIOS
? id === "2A923037-8B2D-487b-9A22-5AC0918ACF3F"
? "message:"
: id === "32D24CB5-7ECE-4606-9C94-19216BA42086"
? "calshow:"
: false
: false;
setDocumentTitle();
useEffect(() => {
setCurrentProductId(id);
}, [id, setCurrentProductId]);
useEffect(() => error && toastr.error(error), [error]);
const appButtons = (
<>
<Badge
label={t("ComingSoon")}
maxWidth="150px"
borderRadius="2px"
className="coming-soon-badge"
/>
<ExternalLink
label={t("ViewWeb")}
onClick={() => {
deleteCookie("desktop_view");
window.open(webLink, "_self", "", true);
}}
/>
{appLink && (
<ExternalLink
label={t("OpenApp", {
title: title,
})}
href={appLink}
/>
)}
</>
);
const moduleDescription = (
<Text className="module-info">
{description}{" "}
{helpUrl && (
<Link
as="a"
href={helpUrl}
target="_blank"
className="learn-more-link"
color="#555F65"
isBold
isHovered
>
{t("LearnMore")}...
</Link>
)}
</Text>
);
return !isLoaded ? (
<></>
) : isMobile ? (
<ComingSoonPage>
<ReactSVG
className="module-logo-icon"
loading={() => (
<Loaders.Rectangle
width="100"
height="14"
backgroundColor="#fff"
foregroundColor="#fff"
backgroundOpacity={0.25}
foregroundOpacity={0.2}
/>
)}
src={imageUrl}
/>
<Box displayProp="flex" flexDirection="column" widthProp="220px">
<Text fontWeight="600" fontSize="19px" className="module-title">
{title}
</Text>
{moduleDescription}
{appButtons}
</Box>
</ComingSoonPage>
) : (
<StyledDesktopContainer
imageSrc={imageUrl}
imageAlt={title}
headerText={title}
descriptionText={moduleDescription}
buttons={appButtons}
/>
);
};
const ComingSoon = (props) => {
return (
<PageLayout>
<PageLayout.SectionBody>
<Body {...props} />
</PageLayout.SectionBody>
</PageLayout>
);
};
ComingSoon.propTypes = {
modules: PropTypes.array,
isLoaded: PropTypes.bool,
};
const ComingSoonWrapper = inject(({ auth }) => ({
modules: auth.moduleStore.modules,
isLoaded: auth.isLoaded,
setCurrentProductId: auth.settingsStore.setCurrentProductId,
}))(withRouter(ComingSoon));
export default (props) => (
<I18nextProvider i18n={i18n}>
<ComingSoonWrapper {...props} />
</I18nextProvider>
);

View File

@ -0,0 +1,39 @@
import { action, makeObservable, observable } from "mobx";
import config from "../../package.json";
import store from "studio/store";
const { auth: authStore } = store;
class CalendarStore {
isLoading = false;
isLoaded = false;
isInit = false;
constructor() {
makeObservable(this, {
isLoading: observable,
isLoaded: observable,
setIsLoading: action,
setIsLoaded: action,
init: action,
});
}
init = async () => {
if (this.isInit) return;
this.isInit = true;
authStore.settingsStore.setModuleInfo(config.homepage, config.id);
this.setIsLoaded(true);
};
setIsLoading = (loading) => {
this.isLoading = loading;
};
setIsLoaded = (isLoaded) => {
this.isLoaded = isLoaded;
};
}
export default CalendarStore;

View File

@ -0,0 +1,12 @@
import React from "react";
if (process.env.NODE_ENV === "development") {
const whyDidYouRender = require("@welldone-software/why-did-you-render");
whyDidYouRender(React, {
trackAllPureComponents: true,
collapseGroups: true,
//trackExtraHooks: [
// [ReactRedux, 'useSelector']
//]
});
}

View File

@ -0,0 +1,198 @@
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack").container
.ModuleFederationPlugin;
const TerserPlugin = require("terser-webpack-plugin");
const { InjectManifest } = require("workbox-webpack-plugin");
const combineUrl = require("@appserver/common/utils/combineUrl");
const AppServerConfig = require("@appserver/common/constants/AppServerConfig");
const path = require("path");
const pkg = require("./package.json");
const deps = pkg.dependencies;
const homepage = pkg.homepage; // combineUrl(AppServerConfig.proxyURL, pkg.homepage);
const title = pkg.title;
var config = {
mode: "development",
entry: "./src/index",
devServer: {
publicPath: homepage,
contentBase: [path.join(__dirname, "dist")],
contentBasePublicPath: homepage,
port: 5017,
historyApiFallback: {
// Paths with dots should still use the history fallback.
// See https://github.com/facebook/create-react-app/issues/387.
disableDotRule: true,
index: homepage,
},
// proxy: [
// {
// context: "/api",
// target: "http://localhost:8092",
// },
// ],
hot: false,
hotOnly: false,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
"Access-Control-Allow-Headers":
"X-Requested-With, content-type, Authorization",
},
},
output: {
publicPath: "auto",
chunkFilename: "static/js/[id].[contenthash].js",
//assetModuleFilename: "static/images/[hash][ext][query]",
path: path.resolve(process.cwd(), "dist"),
filename: "static/js/[name].[contenthash].bundle.js",
},
resolve: {
extensions: [".jsx", ".js", ".json"],
fallback: {
crypto: false,
},
},
module: {
rules: [
{
test: /\.(png|jpe?g|gif|ico)$/i,
type: "asset/resource",
generator: {
filename: "static/images/[hash][ext][query]",
},
},
{
test: /\.m?js/,
type: "javascript/auto",
resolve: {
fullySpecified: false,
},
},
{
test: /\.react.svg$/,
use: [
{
loader: "@svgr/webpack",
options: {
svgoConfig: {
plugins: [{ removeViewBox: false }],
},
},
},
],
},
{ test: /\.json$/, loader: "json-loader" },
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
"style-loader",
// Translates CSS into CommonJS
"css-loader",
// Compiles Sass to CSS
"sass-loader",
],
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-react", "@babel/preset-env"],
plugins: [
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-export-default-from",
],
},
},
"source-map-loader",
],
},
],
},
plugins: [
new CleanWebpackPlugin(),
new ModuleFederationPlugin({
name: "calendar",
filename: "remoteEntry.js",
remotes: {
studio: `studio@${combineUrl(
AppServerConfig.proxyURL,
"/remoteEntry.js"
)}`,
},
exposes: {
"./app": "./src/Calendar.jsx",
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
publicPath: homepage,
title: title,
base: `${homepage}/`,
}),
new CopyPlugin({
patterns: [
{
from: "public",
globOptions: {
dot: true,
gitignore: true,
ignore: ["**/index.html"],
},
},
],
}),
],
};
module.exports = (env, argv) => {
if (argv.mode === "production") {
config.mode = "production";
config.optimization = {
splitChunks: { chunks: "all" },
minimize: true,
minimizer: [new TerserPlugin()],
};
config.plugins.push(
new InjectManifest({
mode: "production", //"development",
swSrc: "@appserver/common/utils/sw-template.js", // this is your sw template file
swDest: "sw.js", // this will be created in the build step
exclude: [/\.map$/, /manifest$/, /service-worker\.js$/],
})
);
} else {
config.devtool = "cheap-module-source-map";
}
return config;
};

View File

@ -0,0 +1,7 @@
{
"presets": ["@babel/preset-react", "@babel/preset-env"],
"plugins": [
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-class-properties"
]
}

View File

@ -0,0 +1,3 @@
PUBLIC_URL=/products/mail
WDS_SOCKET_PATH=/products/mail/sockjs-node
PORT=5016

21
products/ASC.Mail/Client/.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -0,0 +1,15 @@
FROM node:12
WORKDIR /usr/src/app
COPY package.json ./
COPY yarn.lock ./
RUN yarn install
COPY . .
RUN yarn build
EXPOSE 5016
CMD [ "yarn", "build:start" ]

View File

@ -0,0 +1,17 @@
// script to enable webpack-bundle-analyzer
process.env.NODE_ENV = "production";
const webpack = require("webpack");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
const webpackConfigProd = require("react-scripts/config/webpack.config")(
"production"
);
webpackConfigProd.plugins.push(new BundleAnalyzerPlugin());
// actually running compilation and waiting for plugin to start explorer
webpack(webpackConfigProd, (err, stats) => {
if (err || stats.hasErrors()) {
console.error(err);
}
});

View File

@ -0,0 +1,83 @@
{
"name": "@appserver/mail",
"version": "0.1.3",
"private": "true",
"homepage": "/products/mail",
"id": "2a923037-8b2d-487b-9a22-5ac0918acf3f",
"title": "ONLYOFFICE",
"scripts": {
"start": "webpack-cli serve",
"start-prod": "webpack --mode production && serve dist -p 5016",
"build": "webpack --mode production",
"serve": "serve dist -p 5016",
"clean": "rm -rf dist"
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-export-default-from": "^7.12.1",
"@babel/plugin-transform-runtime": "^7.12.1",
"@babel/preset-env": "^7.12.7",
"@babel/preset-react": "^7.12.10",
"@svgr/webpack": "^5.5.0",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^7.0.0",
"css-loader": "^3.6.0",
"html-webpack-plugin": "4.5.0",
"json-loader": "^0.5.7",
"serve": "11.3.2",
"source-map-loader": "^1.1.2",
"style-loader": "1.2.1",
"webpack": "5.14.0",
"webpack-cli": "4.5.0",
"webpack-dev-server": "3.11.2",
"workbox-webpack-plugin": "^6.1.1"
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"attr-accept": "^2.2.2",
"axios": "^0.21.0",
"email-addresses": "^3.1.0",
"i18next": "^19.8.4",
"i18next-http-backend": "^1.1.0",
"mobx": "^6.1.1",
"mobx-react": "^7.1.0",
"moment": "^2.29.1",
"copy-to-clipboard": "^3.2.0",
"fast-deep-equal": "^3.1.3",
"prop-types": "^15.7.2",
"rc-tree": "^2.1.4",
"re-resizable": "^6.9.0",
"react": "^17.0.1",
"react-autosize-textarea": "^7.1.0",
"react-content-loader": "^5.1.4",
"react-custom-scrollbars": "^4.2.1",
"react-device-detect": "^1.14.0",
"react-dom": "^17.0.1",
"react-dropzone": "^11.2.4",
"react-i18next": "^11.7.3",
"react-hammerjs": "^1.0.1",
"react-onclickoutside": "^6.9.0",
"react-player": "^1.15.3",
"react-resize-detector": "^5.2.0",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-string-format": "^0.1.0",
"react-svg": "^12.0.0",
"react-text-mask": "^5.4.3",
"react-toastify": "^6.1.0",
"react-tooltip": "^4.2.11",
"react-viewer": "^3.2.2",
"react-virtualized-auto-sizer": "^1.0.2",
"react-window": "^1.8.6",
"react-window-infinite-loader": "^1.0.5",
"resize-image": "^0.1.0",
"sass": "^1.29.0",
"sass-loader": "^10.1.0",
"sjcl": "^1.0.8",
"screenfull": "^5.1.0",
"styled-components": "^5.2.1",
"workbox-window": "^6.1.1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.2 4H20.8C22.01 4 23 4.97122 23 6.15826V18.8417C23 20.0288 22.01 21 20.8 21H3.2C1.99 21 1 20.0288 1 18.8417V6.15826C1 4.97122 1.99 4 3.2 4ZM12 13L21 8V6L12 11L3 6V8L12 13Z" fill="#7A95B0"/>
</svg>

After

Width:  |  Height:  |  Size: 344 B

View File

@ -0,0 +1,24 @@
<svg width="192" height="192" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.99316 78.0551C1.99316 72.5018 6.49536 68 12.0491 68H179.937C185.491 68 189.993 72.5018 189.993 78.0552V175.945C189.993 181.498 185.491 186 179.937 186H12.0491C6.49537 186 1.99316 181.498 1.99316 175.945V78.0551Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M111.993 123C90.9932 137 98.9932 136 76.9932 123L6 182.957C6.81358 184.792 10.9953 186.06 13.4963 185.994H174.187C180.703 186.06 183.499 185.593 186 183.059L111.993 123Z" fill="#EFEFEF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M153.993 48V94L187.993 71.6216L153.993 48Z" fill="#BDECFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M88.1165 10.7254C86.3404 11.7433 87.064 14.4515 89.1121 14.4515L103.028 14.4515C105.075 14.4515 105.799 11.745 104.025 10.7262L97.0729 6.73522C96.4562 6.38117 95.6974 6.38089 95.0804 6.73447L88.1165 10.7254Z" fill="#BDECFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M37.9932 48V96L3.99316 72.6154L37.9932 48Z" fill="#BDECFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M188.003 176.036V76.9452C188.003 74.3952 186.78 71.9991 184.714 70.4991L100.699 9.51764C97.8975 7.48447 94.1025 7.48447 91.3013 9.51764L7.2862 70.4991C5.21962 71.9991 3.99697 74.3952 3.99697 76.9452V176.036C3.99697 180.438 7.57329 184.007 11.9849 184.007H180.015C184.427 184.007 188.003 180.438 188.003 176.036ZM6.11153 68.8875C3.52831 70.7625 2 73.7577 2 76.9452V176.036C2 181.539 6.47039 186 11.9849 186H180.015C185.53 186 190 181.539 190 176.036V76.9452C190 73.7577 188.472 70.7625 185.888 68.8875L101.873 7.9061C98.3719 5.36463 93.6281 5.36463 90.1267 7.9061L6.11153 68.8875Z" fill="#333333"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M99.9932 132L155.105 93.7683C155.664 93.3915 156 92.755 156 92.0732V16.0317C156 14.9084 155.105 13.9978 154 13.9978H59.4257C58.8953 13.9978 58.3865 14.2121 58.0114 14.5936L36.5858 36.3516C36.2117 36.732 36.0011 37.2476 36 37.7855V94.4637C35.9986 95.1277 36.3161 95.7506 36.8504 96.1322L88.9932 131C92.3975 133.325 95.3444 134.865 99.9932 132Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M99.9932 131.449C97.3871 133.028 96.3382 133.24 93.9932 133C91.6973 132.765 89.8042 132.216 87.9932 131L36.9927 96C35.9241 95.2493 34.0004 93.9062 34.0004 93.9062L34 37.3297C34.0022 36.2718 34.5414 35.2202 35.2894 34.4721L56.5972 13.1636C57.3473 12.4134 58.3648 11.9919 59.4257 11.9919H154C156.209 11.9919 158 13.7828 158 15.9919V92H157.993L99.9932 131.449ZM87.9932 131L36 94.125V37.3339C36.0012 36.805 36.3297 36.2603 36.7037 35.8863L58.0115 14.5778C58.3865 14.2027 58.8953 13.9919 59.4257 13.9919H154C155.104 13.9919 156 14.8874 156 15.9919L155.993 93L99.9932 131.449C95.2589 134.318 91.9932 133 87.9932 131Z" fill="#333333"/>
<path d="M120.545 66.1588C120.545 69.0588 120.099 71.7155 119.208 74.1288C118.316 76.5218 117.059 78.3876 115.434 79.726C113.83 81.0645 111.968 81.7337 109.848 81.7337C108.284 81.7337 106.927 81.3079 105.778 80.4561C104.629 79.6044 103.856 78.4484 103.46 76.9883H103.104C102.133 78.5701 100.935 79.7565 99.5086 80.5474C98.0824 81.3383 96.4779 81.7337 94.6952 81.7337C91.4664 81.7337 88.9211 80.669 87.0591 78.5397C85.217 76.4103 84.2959 73.5305 84.2959 69.9005C84.2959 65.7228 85.524 62.3361 87.9802 59.7403C90.4364 57.1242 93.7345 55.8161 97.8744 55.8161C99.3798 55.8161 101.044 55.9581 102.866 56.242C104.708 56.5056 106.342 56.8808 107.769 57.3675L107.115 71.5127V72.2428C107.115 75.4876 108.145 77.1099 110.205 77.1099C111.77 77.1099 113.008 76.0757 113.919 74.0071C114.85 71.9386 115.315 69.3022 115.315 66.098C115.315 62.6301 114.622 59.5882 113.236 56.9721C111.849 54.3357 109.878 52.3077 107.323 50.8881C104.768 49.4685 101.836 48.7587 98.5281 48.7587C94.3089 48.7587 90.6345 49.651 87.5048 51.4357C84.3949 53.2203 82.018 55.7756 80.3739 59.1014C78.7298 62.4071 77.9078 66.2501 77.9078 70.6305C77.9078 76.5117 79.4429 81.0341 82.5132 84.1977C85.5834 87.3614 89.9907 88.9432 95.7351 88.9432C100.113 88.9432 104.678 88.0306 109.432 86.2054V91.1943C105.273 92.9383 100.747 93.8104 95.854 93.8104C88.5249 93.8104 82.8103 91.7925 78.71 87.7568C74.6097 83.7009 72.5596 78.0529 72.5596 70.813C72.5596 65.52 73.6688 60.8151 75.8873 56.6983C78.1059 52.5612 81.1761 49.3976 85.0981 47.2073C89.04 45.0171 93.4968 43.922 98.4686 43.922C102.767 43.922 106.59 44.8346 109.938 46.6598C113.305 48.485 115.91 51.0909 117.752 54.4776C119.614 57.8441 120.545 61.7378 120.545 66.1588ZM90.0006 70.0221C90.0006 74.7473 91.8131 77.1099 95.438 77.1099C99.261 77.1099 101.351 74.1389 101.707 68.197L102.064 60.9266C100.816 60.5819 99.4788 60.4095 98.0527 60.4095C95.5172 60.4095 93.5364 61.2714 92.1102 62.9952C90.7038 64.719 90.0006 67.0613 90.0006 70.0221Z" fill="url(#paint0_linear)"/>
<path d="M58.0557 14.2578V36.2087H36.0117L58.0557 14.2578Z" fill="url(#paint1_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M58.9301 12.0674C59.7154 12.3852 60 12.9585 60 13.789V35.9465C60 37.0806 59.0591 38 57.8984 38L34.6659 37.9574C34.6659 37.9574 34.0007 37.4469 34 36.6689C34.2179 35.3959 34.9513 34.6132 35.5524 34.0259L56.4124 13.2144C57.0134 12.6272 58.1448 11.7496 58.9301 12.0674ZM57.8984 14.6664L36.7552 35.9465H57.8984V14.6664Z" fill="#333333"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M75.8085 123.172L3.99268 74L5.14338 72.3535L77.993 122.266L89.6805 130.335C92.7316 132.445 96.8174 132.445 99.8685 130.335L111.993 122.266L186.901 71.3129L187.993 73L114.195 123.217L186.499 182.468L185.202 184L112.471 124.378L100.993 132C97.2638 134.579 92.2852 134.579 88.556 132L77.4946 124.364L7.08182 183.65L5.78021 182.123L75.8085 123.172Z" fill="#333333"/>
<defs>
<linearGradient id="paint0_linear" x1="96.5522" y1="43.922" x2="96.5522" y2="93.8104" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF8E3D"/>
<stop offset="1" stop-color="#FF6F3D"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="47.0337" y1="14.2578" x2="47.0337" y2="36.2087" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF8E3D"/>
<stop offset="1" stop-color="#FF6F3D"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -0,0 +1,261 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="shortcut icon" href="favicon.ico" />
<link rel="manifest" href="manifest.json" />
<!-- Tell the browser it's a PWA -->
<meta name="mobile-web-app-capable" content="yes" />
<!-- Tell iOS it's a PWA -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<link rel="apple-touch-icon" href="appIcon.png" />
<link
href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i"
rel="stylesheet"
type="text/css"
/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<style type="text/css">
body {
height: 100%;
margin: 0;
padding: 0;
width: 100%;
font-family: "Open Sans", sans-serif;
font-size: 13px;
-webkit-font-smoothing: antialiased;
}
.temp-header-container {
align-items: center;
background-color: rgb(15, 64, 113);
display: grid;
grid-template-columns: 24px 168px 1fr 36px;
grid-template-rows: 1fr;
grid-column-gap: 16px;
width: calc(100vw - 16px);
height: 56px;
padding-right: 16px;
}
.temp-content-loader {
padding: 16px;
height: calc(100vh - 91px);
display: grid;
grid-template-columns: 250px 1fr;
grid-template-rows: calc(100vh - 91px);
grid-column-gap: 8px;
}
@media (max-width: 1024px) {
.temp-content-loader {
grid-template-columns: 1fr;
}
.temp-content__article {
display: none;
}
}
.burger-loader-svg {
width: 24px;
height: 24px;
padding-left: 16px;
}
.logo-loader-svg {
width: 168px;
height: 24px;
position: relative;
cursor: pointer;
padding-left: 16px;
}
.avatar-loader-svg {
width: 36px;
height: 36px;
}
</style>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript> You need to enable JavaScript to run this app. </noscript>
<div id="root"></div>
<div id="temp-content">
<header class="temp-header-container">
<div id="burger-loader-svg" class="burger-loader-svg">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria0"
preserveAspectRatio="none"
>
<rect
rx="3"
ry="3"
width="24"
height="24"
style="fill: url('#fill0');"
></rect>
<defs>
<linearGradient id="fill0">
<stop
offset="0.599964"
stop-color="#fff"
stop-opacity="0.25"
></stop>
</linearGradient>
</defs>
</svg>
</div>
<div id="logo-loader-svg" class="logo-loader-svg">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria01"
preserveAspectRatio="none"
>
<rect
rx="3"
ry="3"
width="168"
height="24"
style="fill: url('#fill01');"
></rect>
<defs>
<linearGradient id="fill01">
<stop
offset="0.599964"
stop-color="#fff"
stop-opacity="0.25"
></stop>
</linearGradient>
</defs>
</svg>
</div>
<div></div>
<div id="avatar-loader-svg" class="avatar-loader-svg">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria01"
preserveAspectRatio="none"
>
<circle
cx="18"
cy="18"
r="18"
width="36"
height="36"
style="fill: url('#fill02');"
></circle>
<defs>
<linearGradient id="fill02">
<stop
offset="0.599964"
stop-color="#fff"
stop-opacity="0.25"
></stop>
</linearGradient>
</defs>
</svg>
</div>
</header>
<div class="temp-content-loader">
<div class="temp-content__article">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria1"
preserveAspectRatio="none"
>
<rect
x="0"
y="0"
width="100%"
height="100%"
clip-path="url(#clip-path1)"
style="fill: url('#fill1');"
></rect>
<defs>
<clipPath id="clip-path1">
<rect x="3" y="3" rx="5" ry="5" width="100%" />
</clipPath>
<linearGradient id="fill1">
<stop
offset="0.599964"
stop-color="#000000"
stop-opacity="0.1"
></stop>
</linearGradient>
</defs>
</svg>
</div>
<div>
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria2"
preserveAspectRatio="none"
>
<rect
x="0"
y="0"
width="100%"
height="100%"
clip-path="url(#clip-path2)"
style="fill: url('#fill2');"
></rect>
<defs>
<clipPath id="clip-path2">
<rect x="3" y="3" rx="5" ry="5" width="100%" />
</clipPath>
<linearGradient id="fill2">
<stop
offset="0.599964"
stop-color="#000000"
stop-opacity="0.1"
></stop>
</linearGradient>
</defs>
</svg>
</div>
</div>
</div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script>
console.log("It's Mail INIT");
</script>
</body>
</html>

View File

@ -0,0 +1,6 @@
{
"ComingSoon": "Coming soon",
"ViewWeb": "View web version",
"OpenApp": "Open your {{title}} app",
"LearnMore": "Learn more"
}

View File

@ -0,0 +1,6 @@
{
"ComingSoon": "Скоро появится",
"ViewWeb": "Просмотреть веб-версию",
"OpenApp": "Откройте {{title}}",
"LearnMore": "Узнать больше"
}

View File

@ -0,0 +1,15 @@
{
"short_name": "ASC.People",
"name": "ASC.People",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,7 @@
//import "./wdyr";
import React from "react";
import Shell from "studio/shell";
const App = () => <Shell />;
export default App;

View File

@ -0,0 +1,15 @@
import React from "react";
import ReactDOM from "react-dom";
import { MemoryRouter } from "react-router-dom";
import App from "./App";
it("renders without crashing", async () => {
const div = document.createElement("div");
ReactDOM.render(
<MemoryRouter>
<App />
</MemoryRouter>,
div
);
await new Promise((resolve) => setTimeout(resolve, 1000));
});

View File

@ -0,0 +1,71 @@
import React, { useEffect } from "react";
import { Provider as PeopleProvider, inject, observer } from "mobx-react";
import { Switch } from "react-router-dom";
import MailStore from "./store/MailStore";
import ErrorBoundary from "@appserver/common/components/ErrorBoundary";
import toastr from "studio/toastr";
import PrivateRoute from "@appserver/common/components/PrivateRoute";
import AppLoader from "@appserver/common/components/AppLoader";
import { combineUrl, updateTempContent } from "@appserver/common/utils";
import config from "../package.json";
import "./custom.scss";
import i18n from "./i18n";
import { I18nextProvider } from "react-i18next";
import Home from "./pages/Home";
import { AppServerConfig } from "@appserver/common/constants";
const { proxyURL } = AppServerConfig;
const homepage = config.homepage;
const PROXY_HOMEPAGE_URL = combineUrl(proxyURL, homepage);
const Error404 = React.lazy(() => import("studio/Error404"));
const Error404Route = (props) => (
<React.Suspense fallback={<AppLoader />}>
<ErrorBoundary>
<Error404 {...props} />
</ErrorBoundary>
</React.Suspense>
);
const MailContent = (props) => {
const { isLoaded, loadBaseInfo } = props;
useEffect(() => {
loadBaseInfo()
.catch((err) => toastr.error(err))
.finally(() => {
//this.props.setIsLoaded(true);
updateTempContent();
});
}, []);
useEffect(() => {
if (isLoaded) updateTempContent();
}, [isLoaded]);
return (
<Switch>
<PrivateRoute exact path={PROXY_HOMEPAGE_URL} component={Home} />
<PrivateRoute component={Error404Route} />
</Switch>
);
};
const Mail = inject(({ auth, mailStore }) => ({
loadBaseInfo: async () => {
await mailStore.init();
auth.setProductVersion(config.version);
},
isLoaded: auth.isLoaded && mailStore.isLoaded,
}))(observer(MailContent));
const mailStore = new MailStore();
export default (props) => (
<PeopleProvider mailStore={mailStore}>
<I18nextProvider i18n={i18n}>
<Mail {...props} />
</I18nextProvider>
</PeopleProvider>
);

View File

@ -0,0 +1,9 @@
import App from "./App";
import React from "react";
import ReactDOM from "react-dom";
import config from "../package.json";
import { registerSW } from "@appserver/common/utils/sw-helper";
ReactDOM.render(<App />, document.getElementById("root"));
registerSW(config.homepage);

View File

@ -0,0 +1,27 @@
// Override default variables before the import
$font-family-base: "Open Sans", sans-serif;
html,
body {
height: 100%;
}
#root {
min-height: 100%;
position: relative;
.pageLoader {
position: absolute;
left: calc(50% - 20px);
top: 35%;
}
}
body {
margin: 0;
overflow: hidden;
}
body.loading * {
cursor: wait !important;
}

View File

@ -0,0 +1,26 @@
import authStore from "@appserver/common/store/AuthStore";
//import store from "../store/store";
//const { getCurrentProduct } = commonStore.auth.selectors;
export const setDocumentTitle = (subTitle = null) => {
// const state = store.getState();
// const { auth: commonState } = state;
const { isAuthenticated, settingsStore, product: currentModule } = authStore;
const { organizationName } = settingsStore;
let title;
if (subTitle) {
if (isAuthenticated && currentModule) {
title = subTitle + " - " + currentModule.title;
} else {
title = subTitle + " - " + organizationName;
}
} else if (currentModule && organizationName) {
title = currentModule.title + " - " + organizationName;
} else {
title = organizationName;
}
document.title = title;
};

View File

@ -0,0 +1,58 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend";
import config from "../package.json";
import { LANGUAGE } from "@appserver/common/constants";
//import LanguageDetector from "i18next-browser-languagedetector";
// not like to use this?
// have a look at the Quick start guide
// for passing in lng and translations on init
const languages = ["en", "ru"];
i18n
/*
load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
learn more: https://github.com/i18next/i18next-http-backend
*/
.use(Backend)
/*
detect user language
learn more: https://github.com/i18next/i18next-browser-languageDetector
*/
//.use(LanguageDetector)
/*
pass the i18n instance to react-i18next.
*/
.use(initReactI18next)
/*
init i18next
for all options read: https://www.i18next.com/overview/configuration-options
*/
.init({
lng: localStorage.getItem(LANGUAGE) || "en",
supportedLngs: languages,
whitelist: languages,
fallbackLng: "en",
load: "languageOnly",
//debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format) {
if (format === "lowercase") return value.toLowerCase();
return value;
},
},
backend: {
loadPath: `${config.homepage}/locales/{{lng}}/{{ns}}.json`,
},
react: {
useSuspense: false,
},
});
export default i18n;

View File

@ -0,0 +1 @@
import("./bootstrap");

View File

@ -0,0 +1,254 @@
import React, { useEffect } from "react";
import { ReactSVG } from "react-svg";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
import Text from "@appserver/components/text";
import Link from "@appserver/components/link";
import Badge from "@appserver/components/badge";
import Box from "@appserver/components/box";
import EmptyScreenContainer from "@appserver/components/empty-screen-container";
import ExternalLinkIcon from "../../../../../../public/images/external.link.react.svg";
import Loaders from "@appserver/common/components/Loaders";
import toastr from "studio/toastr";
import PageLayout from "@appserver/common/components/PageLayout";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { isMobile, isIOS } from "react-device-detect";
import { setDocumentTitle } from "../../helpers/utils";
import { inject } from "mobx-react";
import i18n from "../../i18n";
import { I18nextProvider } from "react-i18next";
import { combineUrl, deleteCookie } from "@appserver/common/utils";
const commonStyles = `
.link-box {
margin: 8px 0;
.view-web-link {
margin: 8px;
:focus {
outline: 0;
}
}
}
`;
const ComingSoonPage = styled.div`
padding: ${isMobile ? "62px 0 0 0" : "0"};
width: 336px;
margin: 0 auto;
.module-logo-icon {
float: left;
margin-top: 8px;
margin-right: 16px;
}
.module-title {
margin-top: 14px;
margin-bottom: 14px;
}
.module-info {
margin-bottom: 18px;
.learn-more-link {
white-space: nowrap;
}
}
.coming-soon-badge {
margin-bottom: 26px;
}
${commonStyles}
`;
const StyledDesktopContainer = styled(EmptyScreenContainer)`
img {
width: 150px;
height: 150px;
}
span:first-of-type {
font-size: 24px;
}
span {
font-size: 14px;
> p {
font-size: 14px;
.learn-more-link {
white-space: nowrap;
}
}
}
${commonStyles}
.view-web-link {
font-size: 14px;
}
.coming-soon-badge > div > p {
font-size: 13px;
}
`;
const ExternalLink = ({ label, href, onClick }) => (
<Box className="link-box">
<ExternalLinkIcon color="#333333" size={isMobile ? "small" : "medium"} />
<Link
as="a"
href={href}
onClick={onClick}
target="_blank"
className="view-web-link"
color="#555F65"
isBold
isHovered
>
{label}
</Link>
</Box>
);
const Body = ({ modules, match, isLoaded, setCurrentProductId }) => {
const { t } = useTranslation("ComingSoon");
const { error } = match.params;
const { pathname, protocol, hostname } = window.location;
const currentModule = modules.find((m) => m.link === pathname);
const {
id,
title,
description,
imageUrl,
link,
originUrl,
helpUrl,
} = currentModule;
const url = originUrl ? originUrl : link;
const webLink = combineUrl(
protocol + "//" + hostname,
`${url}?desktop_view=true`
);
const appLink = isIOS
? id === "2A923037-8B2D-487b-9A22-5AC0918ACF3F"
? "message:"
: id === "32D24CB5-7ECE-4606-9C94-19216BA42086"
? "calshow:"
: false
: false;
setDocumentTitle();
useEffect(() => {
setCurrentProductId(id);
}, [id, setCurrentProductId]);
useEffect(() => error && toastr.error(error), [error]);
const appButtons = (
<>
<Badge
label={t("ComingSoon")}
maxWidth="150px"
borderRadius="2px"
className="coming-soon-badge"
/>
<ExternalLink
label={t("ViewWeb")}
onClick={() => {
deleteCookie("desktop_view");
window.open(webLink, "_self", "", true);
}}
/>
{appLink && (
<ExternalLink
label={t("OpenApp", {
title: title,
})}
href={appLink}
/>
)}
</>
);
const moduleDescription = (
<Text className="module-info">
{description}{" "}
{helpUrl && (
<Link
as="a"
href={helpUrl}
target="_blank"
className="learn-more-link"
color="#555F65"
isBold
isHovered
>
{t("LearnMore")}...
</Link>
)}
</Text>
);
return !isLoaded ? (
<></>
) : isMobile ? (
<ComingSoonPage>
<ReactSVG
className="module-logo-icon"
loading={() => (
<Loaders.Rectangle
width="100"
height="14"
backgroundColor="#fff"
foregroundColor="#fff"
backgroundOpacity={0.25}
foregroundOpacity={0.2}
/>
)}
src={imageUrl}
/>
<Box displayProp="flex" flexDirection="column" widthProp="220px">
<Text fontWeight="600" fontSize="19px" className="module-title">
{title}
</Text>
{moduleDescription}
{appButtons}
</Box>
</ComingSoonPage>
) : (
<StyledDesktopContainer
imageSrc={imageUrl}
imageAlt={title}
headerText={title}
descriptionText={moduleDescription}
buttons={appButtons}
/>
);
};
const ComingSoon = (props) => {
return (
<PageLayout>
<PageLayout.SectionBody>
<Body {...props} />
</PageLayout.SectionBody>
</PageLayout>
);
};
ComingSoon.propTypes = {
modules: PropTypes.array,
isLoaded: PropTypes.bool,
};
const ComingSoonWrapper = inject(({ auth }) => ({
modules: auth.moduleStore.modules,
isLoaded: auth.isLoaded,
setCurrentProductId: auth.settingsStore.setCurrentProductId,
}))(withRouter(ComingSoon));
export default (props) => (
<I18nextProvider i18n={i18n}>
<ComingSoonWrapper {...props} />
</I18nextProvider>
);

View File

@ -0,0 +1,39 @@
import { action, makeObservable, observable } from "mobx";
import config from "../../package.json";
import store from "studio/store";
const { auth: authStore } = store;
class MailStore {
isLoading = false;
isLoaded = false;
isInit = false;
constructor() {
makeObservable(this, {
isLoading: observable,
isLoaded: observable,
setIsLoading: action,
setIsLoaded: action,
init: action,
});
}
init = async () => {
if (this.isInit) return;
this.isInit = true;
authStore.settingsStore.setModuleInfo(config.homepage, config.id);
this.setIsLoaded(true);
};
setIsLoading = (loading) => {
this.isLoading = loading;
};
setIsLoaded = (isLoaded) => {
this.isLoaded = isLoaded;
};
}
export default MailStore;

View File

@ -0,0 +1,12 @@
import React from "react";
if (process.env.NODE_ENV === "development") {
const whyDidYouRender = require("@welldone-software/why-did-you-render");
whyDidYouRender(React, {
trackAllPureComponents: true,
collapseGroups: true,
//trackExtraHooks: [
// [ReactRedux, 'useSelector']
//]
});
}

View File

@ -0,0 +1,198 @@
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack").container
.ModuleFederationPlugin;
const TerserPlugin = require("terser-webpack-plugin");
const { InjectManifest } = require("workbox-webpack-plugin");
const combineUrl = require("@appserver/common/utils/combineUrl");
const AppServerConfig = require("@appserver/common/constants/AppServerConfig");
const path = require("path");
const pkg = require("./package.json");
const deps = pkg.dependencies;
const homepage = pkg.homepage; // combineUrl(AppServerConfig.proxyURL, pkg.homepage);
const title = pkg.title;
var config = {
mode: "development",
entry: "./src/index",
devServer: {
publicPath: homepage,
contentBase: [path.join(__dirname, "dist")],
contentBasePublicPath: homepage,
port: 5016,
historyApiFallback: {
// Paths with dots should still use the history fallback.
// See https://github.com/facebook/create-react-app/issues/387.
disableDotRule: true,
index: homepage,
},
// proxy: [
// {
// context: "/api",
// target: "http://localhost:8092",
// },
// ],
hot: false,
hotOnly: false,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
"Access-Control-Allow-Headers":
"X-Requested-With, content-type, Authorization",
},
},
output: {
publicPath: "auto",
chunkFilename: "static/js/[id].[contenthash].js",
//assetModuleFilename: "static/images/[hash][ext][query]",
path: path.resolve(process.cwd(), "dist"),
filename: "static/js/[name].[contenthash].bundle.js",
},
resolve: {
extensions: [".jsx", ".js", ".json"],
fallback: {
crypto: false,
},
},
module: {
rules: [
{
test: /\.(png|jpe?g|gif|ico)$/i,
type: "asset/resource",
generator: {
filename: "static/images/[hash][ext][query]",
},
},
{
test: /\.m?js/,
type: "javascript/auto",
resolve: {
fullySpecified: false,
},
},
{
test: /\.react.svg$/,
use: [
{
loader: "@svgr/webpack",
options: {
svgoConfig: {
plugins: [{ removeViewBox: false }],
},
},
},
],
},
{ test: /\.json$/, loader: "json-loader" },
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
"style-loader",
// Translates CSS into CommonJS
"css-loader",
// Compiles Sass to CSS
"sass-loader",
],
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-react", "@babel/preset-env"],
plugins: [
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-export-default-from",
],
},
},
"source-map-loader",
],
},
],
},
plugins: [
new CleanWebpackPlugin(),
new ModuleFederationPlugin({
name: "mail",
filename: "remoteEntry.js",
remotes: {
studio: `studio@${combineUrl(
AppServerConfig.proxyURL,
"/remoteEntry.js"
)}`,
},
exposes: {
"./app": "./src/Mail.jsx",
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
publicPath: homepage,
title: title,
base: `${homepage}/`,
}),
new CopyPlugin({
patterns: [
{
from: "public",
globOptions: {
dot: true,
gitignore: true,
ignore: ["**/index.html"],
},
},
],
}),
],
};
module.exports = (env, argv) => {
if (argv.mode === "production") {
config.mode = "production";
config.optimization = {
splitChunks: { chunks: "all" },
minimize: true,
minimizer: [new TerserPlugin()],
};
config.plugins.push(
new InjectManifest({
mode: "production", //"development",
swSrc: "@appserver/common/utils/sw-template.js", // this is your sw template file
swDest: "sw.js", // this will be created in the build step
exclude: [/\.map$/, /manifest$/, /service-worker\.js$/],
})
);
} else {
config.devtool = "cheap-module-source-map";
}
return config;
};