feat: Version 2

Closes #627, closes #1047
This commit is contained in:
Maksim Eltyshev
2025-05-10 02:09:06 +02:00
parent ad7fb51cfa
commit 2ee1166747
1557 changed files with 76832 additions and 47042 deletions
+126
View File
@@ -0,0 +1,126 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import React, { useCallback, useEffect, useRef } from 'react';
import { useSelector } from 'react-redux';
import { useTranslation, Trans } from 'react-i18next';
import { Loader } from 'semantic-ui-react';
import selectors from '../../../selectors';
import version from '../../../version';
import ModalTypes from '../../../constants/ModalTypes';
import Message from './Message';
import Toaster from '../Toaster';
import Fixed from '../Fixed';
import Static from '../Static';
import AdministrationModal from '../AdministrationModal';
import UserSettingsModal from '../../users/UserSettingsModal';
import ProjectBackground from '../../projects/ProjectBackground';
import AddProjectModal from '../../projects/AddProjectModal';
const Core = React.memo(() => {
const isInitializing = useSelector(selectors.selectIsInitializing);
const isSocketDisconnected = useSelector(selectors.selectIsSocketDisconnected);
const modal = useSelector(selectors.selectCurrentModal);
const project = useSelector(selectors.selectCurrentProject);
const board = useSelector(selectors.selectCurrentBoard);
// TODO: move to selector?
const isNewVersionAvailable = useSelector((state) => {
const config = selectors.selectConfig(state);
return !!config && config.version !== version;
});
const [t] = useTranslation();
const defaultTitleRef = useRef(document.title);
const handleRefreshPageClick = useCallback(() => {
window.location.reload(true);
}, []);
useEffect(() => {
const titleParts = [];
if (project) {
if (board) {
titleParts.push(board.name);
}
titleParts.push(project.name);
}
document.title = titleParts.length === 0 ? defaultTitleRef.current : titleParts.join(' | ');
}, [project, board]);
let modalNode = null;
if (modal) {
switch (modal.type) {
case ModalTypes.ADMINISTRATION:
modalNode = <AdministrationModal />;
break;
case ModalTypes.USER_SETTINGS:
modalNode = <UserSettingsModal />;
break;
case ModalTypes.ADD_PROJECT:
modalNode = <AddProjectModal />;
break;
default:
}
}
let messageNode = null;
if (isSocketDisconnected) {
messageNode = (
<Message
type="error"
header={t('common.noConnectionToServer')}
content={
<Trans i18nKey="common.allChangesWillBeAutomaticallySavedAfterConnectionRestored">
All changes will be automatically saved
<br />
after connection restored
</Trans>
}
/>
);
} else if (isNewVersionAvailable) {
messageNode = (
<Message
type="info"
header={t('common.newVersionAvailable')}
content={
<Trans i18nKey="common.clickHereOrRefreshPageToUpdate">
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid,
jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions */}
<a onClick={handleRefreshPageClick}>Click here</a> or refresh the page to update
</Trans>
}
/>
);
}
return (
<>
{isInitializing ? (
<Loader active size="massive" />
) : (
<>
<Toaster />
{project && project.backgroundType && <ProjectBackground />}
<Fixed />
<Static />
{modalNode}
</>
)}
{messageNode}
</>
);
});
export default Core;
@@ -0,0 +1,31 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import upperFirst from 'lodash/upperFirst';
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import styles from './Message.module.scss';
const Types = {
INFO: 'info',
ERROR: 'error',
};
const Message = React.memo(({ type, header, content }) => (
<div className={classNames(styles.wrapper, styles[`wrapper${upperFirst(type)}`])}>
<div className={styles.header}>{header}</div>
<div className={styles.content}>{content}</div>
</div>
));
Message.propTypes = {
type: PropTypes.oneOf(Object.values(Types)).isRequired,
header: PropTypes.node.isRequired,
content: PropTypes.node.isRequired,
};
export default Message;
@@ -0,0 +1,47 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
:global(#app) {
.content {
color: #fff;
font-size: 16px;
line-height: 1.4;
a {
color: #fff;
cursor: pointer;
text-decoration: underline;
}
}
.header {
color: #fff;
font-size: 24px;
font-weight: bold;
line-height: 1.2;
margin-bottom: 8px;
}
.wrapper {
border-radius: 4px;
bottom: 20px;
max-width: calc(100% - 40px);
padding: 12px 18px;
position: fixed;
right: 20px;
width: 390px;
z-index: 10001;
}
.wrapperError {
background: #cf513d;
box-shadow: #6e2f1a 0 1px 0;
}
.wrapperInfo {
background: #2185d0;
box-shadow: rgba(34, 36, 38, 0.15) 0 1px 0;
}
}
@@ -0,0 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import Core from './Core';
export default Core;