1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-19 05:09:43 +02:00

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

41
client/.gitignore vendored
View file

@ -1,23 +1,24 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View file

@ -1 +0,0 @@
# Planka client

View file

@ -1,58 +0,0 @@
const fs = require('fs');
const path = require('path');
const BASE_URL_PLACEHOLDER = 'BASE_URL_PLACEHOLDER';
const replaceInFile = (file, search, replace) => {
fs.readFile(file, 'utf8', (readError, data) => {
if (readError) {
throw new Error(`${readError}`);
}
const res = data.replaceAll(search, replace);
fs.writeFile(file, res, 'utf8', (writeError) => {
if (writeError) {
throw new Error(`${writeError}`);
}
});
});
};
const replaceBaseUrl = (compiler) => {
compiler.hooks.assetEmitted.tap('ReplaceBaseUrlPlaceholder', (file, info) => {
if (info.content.indexOf(BASE_URL_PLACEHOLDER) >= 0) {
if (/\.css$/.exec(info.targetPath)) {
// For CSS 'url(...)' import we can use relative import
const relPath = path
.relative(path.dirname(info.targetPath), info.outputPath)
.replace(/\\/g, '/');
replaceInFile(info.targetPath, BASE_URL_PLACEHOLDER, `${relPath}/`);
} else if (/\.js$/.exec(info.targetPath)) {
// For JS 'import ... from "some-asset"' we can get the variable injected in the window object
// eslint-disable-next-line no-template-curly-in-string
replaceInFile(info.targetPath, `"${BASE_URL_PLACEHOLDER}"`, '`${window.BASE_URL}/`');
} else if (/index\.html$/.exec(info.targetPath)) {
// For the main html file, we set a placeholder for sails to inject the correct value as runtime
replaceInFile(info.targetPath, BASE_URL_PLACEHOLDER, '<%= BASE_URL %>');
}
}
});
};
module.exports = function override(config, env) {
if (env === 'production') {
const plugins = config.plugins.map((plugin) => {
if (plugin.constructor.name === 'InterpolateHtmlPlugin') {
const newPlugin = plugin;
newPlugin.replacements.PUBLIC_URL = BASE_URL_PLACEHOLDER;
return newPlugin;
}
return plugin;
});
return {
...config,
output: { ...config.output, publicPath: BASE_URL_PLACEHOLDER },
plugins: [...plugins, { apply: replaceBaseUrl }],
};
}
return config;
};

18
client/index.html Normal file
View file

@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="PLANKA is the kanban-style project mastering tool for everyone" />
<title>PLANKA</title>
<link rel="icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
</head>
<body id="app"></body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.js"></script>
</body>
</html>

21148
client/package-lock.json generated

File diff suppressed because it is too large Load diff

160
client/package.json Normal file → Executable file
View file

@ -1,24 +1,18 @@
{
"name": "planka-client",
"private": true,
"type": "module",
"scripts": {
"build": "react-app-rewired build",
"eject": "react-scripts eject",
"lint": "eslint --ext js,jsx src config-overrides.js",
"start": "react-app-rewired start",
"test": "react-app-rewired test",
"test:acceptance": "cucumber-js --require tests/acceptance/cucumber.conf.js --require tests/acceptance/stepDefinitions/**/*.js --format @cucumber/pretty-formatter"
"build": "vite build",
"postinstall": "patch-package",
"lint": "eslint --ext js,jsx src --report-unused-disable-directives",
"start": "vite",
"test": "jest",
"test:acceptance": "cucumber-js --import tests/acceptance/cucumber.conf.js --import tests/acceptance/steps/**/*.js --format @cucumber/pretty-formatter tests"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
"babel": {
"presets": [
"@babel/preset-env"
]
},
"eslintConfig": {
@ -35,96 +29,154 @@
},
"requireConfigFile": false
},
"plugins": [
"prettier"
],
"extends": [
"airbnb",
"airbnb/hooks",
"../package.json"
"plugin:prettier/recommended"
],
"rules": {
"no-unused-vars": "warn",
"import/no-extraneous-dependencies": [
"import/no-unresolved": [
"error",
{
"devDependencies": [
"src/setupTests.js",
"**/*.test.js"
"ignore": [
"\\.svg\\?react$"
]
}
],
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
"endOfLine": "auto",
"printWidth": 100,
"singleQuote": true,
"trailingComma": "all"
}
]
},
"overrides": [
{
"files": [
"tests/acceptance/**/*.js"
],
"rules": {
"import/extensions": "off"
},
"globals": {
"browser": "readonly",
"context": "readonly",
"page": "readonly"
}
}
]
},
"jest": {
"transform": {
"^.+\\.(js|jsx)$": "babel-jest"
}
},
"dependencies": {
"@ballerina/highlightjs-ballerina": "^1.0.1",
"@diplodoc/cut-extension": "^0.7.3",
"@diplodoc/transform": "^4.57.2",
"@gravity-ui/markdown-editor": "^15.11.0",
"@gravity-ui/uikit": "^7.11.0",
"@juggle/resize-observer": "^3.4.0",
"@vitejs/plugin-react": "^4.4.1",
"browserslist-to-esbuild": "^2.1.1",
"classnames": "^2.5.1",
"date-fns": "^2.30.0",
"dequal": "^2.0.3",
"easymde": "^2.18.0",
"highlight.js": "^11.11.1",
"highlightjs-4d": "^1.0.6",
"highlightjs-alan": "^0.0.2",
"highlightjs-apex": "^1.5.0",
"highlightjs-blade": "^0.1.0",
"highlightjs-cobol": "^0.3.3",
"highlightjs-cshtml-razor": "^2.1.1",
"highlightjs-gf": "^1.0.1",
"highlightjs-jolie": "^0.1.8",
"highlightjs-lean": "^1.2.0",
"highlightjs-lookml": "^1.0.2",
"highlightjs-macaulay2": "^0.2.5",
"highlightjs-mlir": "^0.0.1",
"highlightjs-qsharp": "^1.0.2",
"highlightjs-redbol": "^2.1.2",
"highlightjs-rpm-specfile": "^1.0.0",
"highlightjs-sap-abap": "^0.3.0",
"highlightjs-solidity": "^2.0.6",
"highlightjs-supercollider": "^1.0.0",
"highlightjs-svelte": "^1.0.6",
"highlightjs-xsharp": "^1.0.0",
"highlightjs-zenscript": "^2.0.0",
"hightlightjs-papyrus": "^0.0.4",
"history": "^5.3.0",
"i18next": "23.15.2",
"i18next-browser-languagedetector": "^8.0.0",
"i18next": "^23.16.8",
"i18next-browser-languagedetector": "^8.1.0",
"initials": "^3.1.2",
"javascript-time-ago": "^2.5.11",
"js-cookie": "^3.0.5",
"jwt-decode": "^4.0.0",
"linkify-react": "^4.1.4",
"linkifyjs": "^4.1.4",
"linkify-react": "^4.3.1",
"linkifyjs": "^4.3.1",
"lodash": "^4.17.21",
"nanoid": "^5.0.9",
"node-sass": "^9.0.0",
"lowlight": "^3.3.0",
"markdown-it": "^13.0.2",
"nanoid": "^5.1.5",
"patch-package": "^8.0.0",
"photoswipe": "^5.4.4",
"prop-types": "^15.8.1",
"react": "18.2.0",
"react-app-rewired": "^2.2.1",
"react-beautiful-dnd": "^13.1.1",
"react-datepicker": "^4.25.0",
"react-dom": "18.2.0",
"react-dropzone": "^14.3.5",
"react-i18next": "^15.1.1",
"react-dropzone": "^14.3.8",
"react-frame-component": "^5.2.7",
"react-hot-toast": "^2.5.2",
"react-i18next": "^15.5.1",
"react-input-mask": "^2.0.4",
"react-markdown": "^8.0.7",
"react-intersection-observer": "^9.16.0",
"react-photoswipe-gallery": "^2.2.7",
"react-redux": "^8.1.3",
"react-router-dom": "^6.28.0",
"react-scripts": "5.0.1",
"react-simplemde-editor": "^5.2.0",
"react-textarea-autosize": "^8.5.5",
"react-router-dom": "^6.30.0",
"react-textarea-autosize": "^8.5.9",
"react-time-ago": "^7.3.3",
"redux": "^4.2.1",
"redux-logger": "^3.0.6",
"redux-orm": "^0.16.2",
"redux-saga": "^1.3.0",
"remark-breaks": "^4.0.0",
"remark-gfm": "^3.0.1",
"reselect": "^4.1.8",
"sails.io.js": "^1.2.1",
"sass-embedded": "^1.87.0",
"semantic-ui-react": "^2.1.5",
"socket.io-client": "^2.5.0",
"validator": "^13.12.0",
"whatwg-fetch": "^3.6.20",
"validator": "^13.15.0",
"vite": "^5.4.19",
"vite-plugin-commonjs": "^0.10.4",
"vite-plugin-node-polyfills": "^0.23.0",
"vite-plugin-svgr": "^4.3.0",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@cucumber/cucumber": "^7.3.2",
"@babel/eslint-parser": "^7.27.1",
"@babel/preset-env": "^7.27.2",
"@cucumber/cucumber": "^11.2.0",
"@cucumber/pretty-formatter": "^1.0.1",
"@playwright/test": "^1.49.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^15.0.7",
"@testing-library/user-event": "^14.5.2",
"axios": "^1.8.2",
"@playwright/test": "^1.52.0",
"babel-jest": "^29.7.0",
"babel-preset-airbnb": "^5.0.0",
"chai": "^4.5.0",
"eslint": "8.57.0",
"eslint": "^8.57.1",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-prettier": "^5.4.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^4.6.2",
"playwright": "^1.49.0",
"react-test-renderer": "18.2.0"
"jest": "^29.7.0",
"playwright": "^1.52.0",
"prettier": "3.3.3"
}
}

View file

@ -0,0 +1,53 @@
diff --git a/node_modules/@gravity-ui/markdown-editor/build/esm/bundle/wysiwyg-preset.js b/node_modules/@gravity-ui/markdown-editor/build/esm/bundle/wysiwyg-preset.js
index 2152fd6..ceda0c1 100644
--- a/node_modules/@gravity-ui/markdown-editor/build/esm/bundle/wysiwyg-preset.js
+++ b/node_modules/@gravity-ui/markdown-editor/build/esm/bundle/wysiwyg-preset.js
@@ -101,7 +101,6 @@ export const BundlePreset = (builder, opts) => {
enableNewImageSizeCalculation: opts.enableNewImageSizeCalculation,
...opts.imgSize,
},
- checkbox: { checkboxLabelPlaceholder: () => i18nPlaceholder('checkbox'), ...opts.checkbox },
deflist: {
deflistTermPlaceholder: () => i18nPlaceholder('deflist_term'),
deflistDescPlaceholder: () => i18nPlaceholder('deflist_desc'),
@@ -118,11 +117,6 @@ export const BundlePreset = (builder, opts) => {
...opts.yfmNote,
},
yfmTable: { yfmTableCellPlaceholder: () => i18nPlaceholder('table_cell'), ...opts.yfmTable },
- yfmFile: {
- fileUploadHandler: opts.fileUploadHandler,
- needToSetDimensionsForUploadedImages: opts.needToSetDimensionsForUploadedImages,
- ...opts.yfmFile,
- },
yfmHeading: {
h1Key: f.toPM(A.Heading1),
h2Key: f.toPM(A.Heading2),
diff --git a/node_modules/@gravity-ui/markdown-editor/build/esm/presets/yfm.js b/node_modules/@gravity-ui/markdown-editor/build/esm/presets/yfm.js
index ed2a9db..f95b693 100644
--- a/node_modules/@gravity-ui/markdown-editor/build/esm/presets/yfm.js
+++ b/node_modules/@gravity-ui/markdown-editor/build/esm/presets/yfm.js
@@ -1,5 +1,5 @@
import { Deflist, Subscript, Superscript, Underline, } from "../extensions/markdown/index.js";
-import { Checkbox, ImgSize, Monospace, Video, YfmConfigs, YfmCut, YfmFile, YfmHeading, YfmNote, YfmTable, YfmTabs, } from "../extensions/yfm/index.js";
+import { ImgSize, Monospace, Video, YfmConfigs, YfmCut, YfmHeading, YfmNote, YfmTable } from "../extensions/yfm/index.js";
import { DefaultPreset } from "./default.js";
export const YfmPreset = (builder, opts) => {
builder.use(DefaultPreset, { ...opts, image: false, heading: false });
@@ -8,16 +8,13 @@ export const YfmPreset = (builder, opts) => {
.use(Subscript)
.use(Superscript)
.use(Underline, opts.underline ?? {})
- .use(Checkbox, opts.checkbox ?? {})
.use(ImgSize, opts.imgSize ?? {})
.use(Monospace)
.use(Video, opts.video ?? {})
.use(YfmConfigs, opts.yfmConfigs ?? {})
.use(YfmCut, opts.yfmCut ?? {})
.use(YfmNote, opts.yfmNote ?? {})
- .use(YfmFile, opts.yfmFile ?? {})
.use(YfmHeading, opts.yfmHeading ?? {})
- .use(YfmTable, opts.yfmTable ?? {})
- .use(YfmTabs);
+ .use(YfmTable, opts.yfmTable ?? {});
};
//# sourceMappingURL=yfm.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,37 @@
diff --git a/node_modules/semantic-ui-react/dist/es/lib/doesNodeContainClick.js b/node_modules/semantic-ui-react/dist/es/lib/doesNodeContainClick.js
index 6d06078..fb7534d 100644
--- a/node_modules/semantic-ui-react/dist/es/lib/doesNodeContainClick.js
+++ b/node_modules/semantic-ui-react/dist/es/lib/doesNodeContainClick.js
@@ -17,13 +17,7 @@ var doesNodeContainClick = function doesNodeContainClick(node, e) {
if (_some([e, node], _isNil)) return false; // if there is an e.target and it is in the document, use a simple node.contains() check
if (e.target) {
- _invoke(e.target, 'setAttribute', 'data-suir-click-target', true);
-
- if (document.querySelector('[data-suir-click-target=true]')) {
- _invoke(e.target, 'removeAttribute', 'data-suir-click-target');
-
- return node.contains(e.target);
- }
+ return node.contains(e.target);
} // Below logic handles cases where the e.target is no longer in the document.
// The result of the click likely has removed the e.target node.
// Instead of node.contains(), we'll identify the click by X/Y position.
diff --git a/node_modules/semantic-ui-react/src/lib/doesNodeContainClick.js b/node_modules/semantic-ui-react/src/lib/doesNodeContainClick.js
index d1ae271..43e1170 100644
--- a/node_modules/semantic-ui-react/src/lib/doesNodeContainClick.js
+++ b/node_modules/semantic-ui-react/src/lib/doesNodeContainClick.js
@@ -14,12 +14,7 @@ const doesNodeContainClick = (node, e) => {
// if there is an e.target and it is in the document, use a simple node.contains() check
if (e.target) {
- _.invoke(e.target, 'setAttribute', 'data-suir-click-target', true)
-
- if (document.querySelector('[data-suir-click-target=true]')) {
- _.invoke(e.target, 'removeAttribute', 'data-suir-click-target')
- return node.contains(e.target)
- }
+ return node.contains(e.target)
}
// Below logic handles cases where the e.target is no longer in the document.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 143 KiB

Before After
Before After

View file

@ -1,44 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Planka is an open source project management software"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
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`.
-->
<title>Planka</title>
</head>
<script>window.BASE_URL = "%PUBLIC_URL%";</script>
<body id="app">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></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`.
-->
</body>
</html>

BIN
client/public/logo192.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Before After
Before After

BIN
client/public/logo512.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before After
Before After

View file

@ -1,5 +1,5 @@
{
"name": "Planka",
"name": "PLANKA",
"icons": [
{
"src": "favicon.ico",

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const fetchActivities = (cardId) => ({
@ -24,31 +29,6 @@ fetchActivities.failure = (cardId, error) => ({
},
});
const toggleActivitiesDetails = (cardId, isVisible) => ({
type: ActionTypes.ACTIVITIES_DETAILS_TOGGLE,
payload: {
cardId,
isVisible,
},
});
toggleActivitiesDetails.success = (cardId, activities, users) => ({
type: ActionTypes.ACTIVITIES_DETAILS_TOGGLE__SUCCESS,
payload: {
cardId,
activities,
users,
},
});
toggleActivitiesDetails.failure = (cardId, error) => ({
type: ActionTypes.ACTIVITIES_DETAILS_TOGGLE__FAILURE,
payload: {
cardId,
error,
},
});
const handleActivityCreate = (activity) => ({
type: ActionTypes.ACTIVITY_CREATE_HANDLE,
payload: {
@ -56,24 +36,7 @@ const handleActivityCreate = (activity) => ({
},
});
const handleActivityUpdate = (activity) => ({
type: ActionTypes.ACTIVITY_UPDATE_HANDLE,
payload: {
activity,
},
});
const handleActivityDelete = (activity) => ({
type: ActionTypes.ACTIVITY_DELETE_HANDLE,
payload: {
activity,
},
});
export default {
fetchActivities,
toggleActivitiesDetails,
handleActivityCreate,
handleActivityUpdate,
handleActivityDelete,
};

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createAttachment = (attachment) => ({

View file

@ -0,0 +1,72 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createBackgroundImage = (backgroundImage) => ({
type: ActionTypes.BACKGROUND_IMAGE_CREATE,
payload: {
backgroundImage,
},
});
createBackgroundImage.success = (localId, backgroundImage) => ({
type: ActionTypes.BACKGROUND_IMAGE_CREATE__SUCCESS,
payload: {
localId,
backgroundImage,
},
});
createBackgroundImage.failure = (localId, error) => ({
type: ActionTypes.BACKGROUND_IMAGE_CREATE__FAILURE,
payload: {
localId,
error,
},
});
const handleBackgroundImageCreate = (backgroundImage) => ({
type: ActionTypes.BACKGROUND_IMAGE_CREATE_HANDLE,
payload: {
backgroundImage,
},
});
const deleteBackgroundImage = (id) => ({
type: ActionTypes.BACKGROUND_IMAGE_DELETE,
payload: {
id,
},
});
deleteBackgroundImage.success = (backgroundImage) => ({
type: ActionTypes.BACKGROUND_IMAGE_DELETE__SUCCESS,
payload: {
backgroundImage,
},
});
deleteBackgroundImage.failure = (id, error) => ({
type: ActionTypes.BACKGROUND_IMAGE_DELETE__FAILURE,
payload: {
id,
error,
},
});
const handleBackgroundImageDelete = (backgroundImage) => ({
type: ActionTypes.BACKGROUND_IMAGE_DELETE_HANDLE,
payload: {
backgroundImage,
},
});
export default {
createBackgroundImage,
handleBackgroundImageCreate,
deleteBackgroundImage,
handleBackgroundImageDelete,
};

View file

@ -0,0 +1,104 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createBaseCustomFieldGroup = (baseCustomFieldGroup) => ({
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE,
payload: {
baseCustomFieldGroup,
},
});
createBaseCustomFieldGroup.success = (localId, baseCustomFieldGroup) => ({
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE__SUCCESS,
payload: {
localId,
baseCustomFieldGroup,
},
});
createBaseCustomFieldGroup.failure = (localId, error) => ({
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE__FAILURE,
payload: {
localId,
error,
},
});
const handleBaseCustomFieldGroupCreate = (baseCustomFieldGroup) => ({
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE_HANDLE,
payload: {
baseCustomFieldGroup,
},
});
const updateBaseCustomFieldGroup = (id, data) => ({
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE,
payload: {
id,
data,
},
});
updateBaseCustomFieldGroup.success = (baseCustomFieldGroup) => ({
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE__SUCCESS,
payload: {
baseCustomFieldGroup,
},
});
updateBaseCustomFieldGroup.failure = (id, error) => ({
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE__FAILURE,
payload: {
id,
error,
},
});
const handleBaseCustomFieldGroupUpdate = (baseCustomFieldGroup) => ({
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE_HANDLE,
payload: {
baseCustomFieldGroup,
},
});
const deleteBaseCustomFieldGroup = (id) => ({
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE,
payload: {
id,
},
});
deleteBaseCustomFieldGroup.success = (baseCustomFieldGroup) => ({
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE__SUCCESS,
payload: {
baseCustomFieldGroup,
},
});
deleteBaseCustomFieldGroup.failure = (id, error) => ({
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE__FAILURE,
payload: {
id,
error,
},
});
const handleBaseCustomFieldGroupDelete = (baseCustomFieldGroup) => ({
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE_HANDLE,
payload: {
baseCustomFieldGroup,
},
});
export default {
createBaseCustomFieldGroup,
handleBaseCustomFieldGroupCreate,
updateBaseCustomFieldGroup,
handleBaseCustomFieldGroupUpdate,
deleteBaseCustomFieldGroup,
handleBaseCustomFieldGroupDelete,
};

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createBoardMembership = (boardMembership) => ({
@ -25,10 +30,13 @@ createBoardMembership.failure = (localId, error) => ({
const handleBoardMembershipCreate = (
boardMembership,
isProjectAvailable,
project,
board,
users,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
@ -36,17 +44,25 @@ const handleBoardMembershipCreate = (
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
deletedNotifications,
customFieldGroups,
customFields,
customFieldValues,
notificationsToDelete,
notificationServices,
) => ({
type: ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE,
payload: {
boardMembership,
isProjectAvailable,
project,
board,
users,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
@ -54,18 +70,14 @@ const handleBoardMembershipCreate = (
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
deletedNotifications,
},
});
handleBoardMembershipCreate.fetchProject = (id, currentUserId, currentBoardId) => ({
type: ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE__PROJECT_FETCH,
payload: {
id,
currentUserId,
currentBoardId,
customFieldGroups,
customFields,
customFieldValues,
notificationsToDelete,
notificationServices,
},
});
@ -99,17 +111,19 @@ const handleBoardMembershipUpdate = (boardMembership) => ({
},
});
const deleteBoardMembership = (id) => ({
const deleteBoardMembership = (id, isCurrentUser) => ({
type: ActionTypes.BOARD_MEMBERSHIP_DELETE,
payload: {
id,
isCurrentUser,
},
});
deleteBoardMembership.success = (boardMembership) => ({
deleteBoardMembership.success = (boardMembership, isCurrentUser) => ({
type: ActionTypes.BOARD_MEMBERSHIP_DELETE__SUCCESS,
payload: {
boardMembership,
isCurrentUser,
},
});
@ -121,10 +135,11 @@ deleteBoardMembership.failure = (id, error) => ({
},
});
const handleBoardMembershipDelete = (boardMembership) => ({
const handleBoardMembershipDelete = (boardMembership, isCurrentUser) => ({
type: ActionTypes.BOARD_MEMBERSHIP_DELETE_HANDLE,
payload: {
boardMembership,
isCurrentUser,
},
});

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createBoard = (board) => ({
@ -24,10 +29,11 @@ createBoard.failure = (localId, error) => ({
},
});
const handleBoardCreate = (board) => ({
const handleBoardCreate = (board, boardMemberships) => ({
type: ActionTypes.BOARD_CREATE_HANDLE,
payload: {
board,
boardMemberships,
},
});
@ -48,8 +54,12 @@ fetchBoard.success = (
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
) => ({
type: ActionTypes.BOARD_FETCH__SUCCESS,
payload: {
@ -62,8 +72,12 @@ fetchBoard.success = (
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
},
});
@ -105,6 +119,23 @@ const handleBoardUpdate = (board) => ({
},
});
const updateBoardContext = (id, value) => ({
type: ActionTypes.BOARD_CONTEXT_UPDATE,
payload: {
id,
value,
},
});
const searchInBoard = (id, value, currentListId) => ({
type: ActionTypes.IN_BOARD_SEARCH,
payload: {
id,
value,
currentListId,
},
});
const deleteBoard = (id) => ({
type: ActionTypes.BOARD_DELETE,
payload: {
@ -140,6 +171,8 @@ export default {
fetchBoard,
updateBoard,
handleBoardUpdate,
updateBoardContext,
searchInBoard,
deleteBoard,
handleBoardDelete,
};

View file

@ -1,9 +1,67 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createCard = (card) => ({
const fetchCards = (listId) => ({
type: ActionTypes.CARDS_FETCH,
payload: {
listId,
},
});
fetchCards.success = (
listId,
cards,
users,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
) => ({
type: ActionTypes.CARDS_FETCH__SUCCESS,
payload: {
listId,
cards,
users,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
},
});
fetchCards.failure = (listId, error) => ({
type: ActionTypes.CARDS_FETCH__FAILURE,
payload: {
listId,
error,
},
});
const handleCardsUpdate = (cards, activities) => ({
type: ActionTypes.CARDS_UPDATE_HANDLE,
payload: {
cards,
activities,
},
});
const createCard = (card, autoOpen) => ({
type: ActionTypes.CARD_CREATE,
payload: {
card,
autoOpen,
},
});
@ -23,14 +81,30 @@ createCard.failure = (localId, error) => ({
},
});
const handleCardCreate = (card, cardMemberships, cardLabels, tasks, attachments) => ({
const handleCardCreate = (
card,
users,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
) => ({
type: ActionTypes.CARD_CREATE_HANDLE,
payload: {
card,
users,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
},
});
@ -57,42 +131,75 @@ updateCard.failure = (id, error) => ({
},
});
const handleCardUpdate = (card, isFetched, cardMemberships, cardLabels, tasks, attachments) => ({
const handleCardUpdate = (
card,
isFetched,
users,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
) => ({
type: ActionTypes.CARD_UPDATE_HANDLE,
payload: {
card,
isFetched,
users,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
},
});
const duplicateCard = (id, card, taskIds) => ({
const duplicateCard = (id, localId, data) => ({
type: ActionTypes.CARD_DUPLICATE,
payload: {
id,
card,
taskIds,
localId,
data,
},
});
duplicateCard.success = (localId, card, cardMemberships, cardLabels, tasks) => ({
duplicateCard.success = (
localId,
card,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
) => ({
type: ActionTypes.CARD_DUPLICATE__SUCCESS,
payload: {
localId,
card,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
},
});
duplicateCard.failure = (id, error) => ({
duplicateCard.failure = (localId, error) => ({
type: ActionTypes.CARD_DUPLICATE__FAILURE,
payload: {
id,
localId,
error,
},
});
@ -126,15 +233,9 @@ const handleCardDelete = (card) => ({
},
});
const filterText = (boardId, text) => ({
type: ActionTypes.TEXT_FILTER_IN_CURRENT_BOARD,
payload: {
boardId,
text,
},
});
export default {
fetchCards,
handleCardsUpdate,
createCard,
handleCardCreate,
updateCard,
@ -142,5 +243,4 @@ export default {
duplicateCard,
deleteCard,
handleCardDelete,
filterText,
};

View file

@ -1,75 +0,0 @@
import ActionTypes from '../constants/ActionTypes';
const createCommentActivity = (activity) => ({
type: ActionTypes.COMMENT_ACTIVITY_CREATE,
payload: {
activity,
},
});
createCommentActivity.success = (localId, activity) => ({
type: ActionTypes.COMMENT_ACTIVITY_CREATE__SUCCESS,
payload: {
localId,
activity,
},
});
createCommentActivity.failure = (localId, error) => ({
type: ActionTypes.COMMENT_ACTIVITY_CREATE__FAILURE,
payload: {
localId,
error,
},
});
const updateCommentActivity = (id, data) => ({
type: ActionTypes.COMMENT_ACTIVITY_UPDATE,
payload: {
id,
data,
},
});
updateCommentActivity.success = (activity) => ({
type: ActionTypes.COMMENT_ACTIVITY_UPDATE__SUCCESS,
payload: {
activity,
},
});
updateCommentActivity.failure = (id, error) => ({
type: ActionTypes.COMMENT_ACTIVITY_UPDATE__FAILURE,
payload: {
id,
error,
},
});
const deleteCommentActivity = (id) => ({
type: ActionTypes.COMMENT_ACTIVITY_DELETE,
payload: {
id,
},
});
deleteCommentActivity.success = (activity) => ({
type: ActionTypes.COMMENT_ACTIVITY_DELETE__SUCCESS,
payload: {
activity,
},
});
deleteCommentActivity.failure = (id, error) => ({
type: ActionTypes.COMMENT_ACTIVITY_DELETE__FAILURE,
payload: {
id,
error,
},
});
export default {
createCommentActivity,
updateCommentActivity,
deleteCommentActivity,
};

View file

@ -0,0 +1,130 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const fetchComments = (cardId) => ({
type: ActionTypes.COMMENTS_FETCH,
payload: {
cardId,
},
});
fetchComments.success = (cardId, comments, users) => ({
type: ActionTypes.COMMENTS_FETCH__SUCCESS,
payload: {
cardId,
comments,
users,
},
});
fetchComments.failure = (cardId, error) => ({
type: ActionTypes.COMMENTS_FETCH__FAILURE,
payload: {
cardId,
error,
},
});
const createComment = (comment) => ({
type: ActionTypes.COMMENT_CREATE,
payload: {
comment,
},
});
createComment.success = (localId, comment) => ({
type: ActionTypes.COMMENT_CREATE__SUCCESS,
payload: {
localId,
comment,
},
});
createComment.failure = (localId, error) => ({
type: ActionTypes.COMMENT_CREATE__FAILURE,
payload: {
localId,
error,
},
});
const handleCommentCreate = (comment, users) => ({
type: ActionTypes.COMMENT_CREATE_HANDLE,
payload: {
comment,
users,
},
});
const updateComment = (id, data) => ({
type: ActionTypes.COMMENT_UPDATE,
payload: {
id,
data,
},
});
updateComment.success = (comment) => ({
type: ActionTypes.COMMENT_UPDATE__SUCCESS,
payload: {
comment,
},
});
updateComment.failure = (id, error) => ({
type: ActionTypes.COMMENT_UPDATE__FAILURE,
payload: {
id,
error,
},
});
const handleCommentUpdate = (comment) => ({
type: ActionTypes.COMMENT_UPDATE_HANDLE,
payload: {
comment,
},
});
const deleteComment = (id) => ({
type: ActionTypes.COMMENT_DELETE,
payload: {
id,
},
});
deleteComment.success = (comment) => ({
type: ActionTypes.COMMENT_DELETE__SUCCESS,
payload: {
comment,
},
});
deleteComment.failure = (id, error) => ({
type: ActionTypes.COMMENT_DELETE__FAILURE,
payload: {
id,
error,
},
});
const handleCommentDelete = (comment) => ({
type: ActionTypes.COMMENT_DELETE_HANDLE,
payload: {
comment,
},
});
export default {
fetchComments,
createComment,
handleCommentCreate,
updateComment,
handleCommentUpdate,
deleteComment,
handleCommentDelete,
};

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const initializeCore = (
@ -6,6 +11,8 @@ const initializeCore = (
users,
projects,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
@ -13,10 +20,14 @@ const initializeCore = (
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
activities,
customFieldGroups,
customFields,
customFieldValues,
notifications,
notificationServices,
) => ({
type: ActionTypes.CORE_INITIALIZE,
payload: {
@ -25,6 +36,8 @@ const initializeCore = (
users,
projects,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
@ -32,14 +45,17 @@ const initializeCore = (
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
activities,
customFieldGroups,
customFields,
customFieldValues,
notifications,
notificationServices,
},
});
// TODO: with success?
initializeCore.fetchConfig = (config) => ({
type: ActionTypes.CORE_INITIALIZE__CONFIG_FETCH,
payload: {
@ -47,13 +63,32 @@ initializeCore.fetchConfig = (config) => ({
},
});
const logout = (invalidateAccessToken) => ({
type: ActionTypes.LOGOUT,
const toggleFavorites = (isEnabled) => ({
type: ActionTypes.FAVORITES_TOGGLE,
payload: {
invalidateAccessToken,
isEnabled,
},
});
const toggleEditMode = (isEnabled) => ({
type: ActionTypes.EDIT_MODE_TOGGLE,
payload: {
isEnabled,
},
});
const updateHomeView = (value) => ({
type: ActionTypes.HOME_VIEW_UPDATE,
payload: {
value,
},
});
const logout = () => ({
type: ActionTypes.LOGOUT,
payload: {},
});
logout.invalidateAccessToken = () => ({
type: ActionTypes.LOGOUT__ACCESS_TOKEN_INVALIDATE,
payload: {},
@ -61,5 +96,8 @@ logout.invalidateAccessToken = () => ({
export default {
initializeCore,
toggleFavorites,
toggleEditMode,
updateHomeView,
logout,
};

View file

@ -0,0 +1,104 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createCustomFieldGroup = (customFieldGroup) => ({
type: ActionTypes.CUSTOM_FIELD_GROUP_CREATE,
payload: {
customFieldGroup,
},
});
createCustomFieldGroup.success = (localId, customFieldGroup) => ({
type: ActionTypes.CUSTOM_FIELD_GROUP_CREATE__SUCCESS,
payload: {
localId,
customFieldGroup,
},
});
createCustomFieldGroup.failure = (localId, error) => ({
type: ActionTypes.CUSTOM_FIELD_GROUP_CREATE__FAILURE,
payload: {
localId,
error,
},
});
const handleCustomFieldGroupCreate = (customFieldGroup) => ({
type: ActionTypes.CUSTOM_FIELD_GROUP_CREATE_HANDLE,
payload: {
customFieldGroup,
},
});
const updateCustomFieldGroup = (id, data) => ({
type: ActionTypes.CUSTOM_FIELD_GROUP_UPDATE,
payload: {
id,
data,
},
});
updateCustomFieldGroup.success = (customFieldGroup) => ({
type: ActionTypes.CUSTOM_FIELD_GROUP_UPDATE__SUCCESS,
payload: {
customFieldGroup,
},
});
updateCustomFieldGroup.failure = (id, error) => ({
type: ActionTypes.CUSTOM_FIELD_GROUP_UPDATE__FAILURE,
payload: {
id,
error,
},
});
const handleCustomFieldGroupUpdate = (customFieldGroup) => ({
type: ActionTypes.CUSTOM_FIELD_GROUP_UPDATE_HANDLE,
payload: {
customFieldGroup,
},
});
const deleteCustomFieldGroup = (id) => ({
type: ActionTypes.CUSTOM_FIELD_GROUP_DELETE,
payload: {
id,
},
});
deleteCustomFieldGroup.success = (customFieldGroup) => ({
type: ActionTypes.CUSTOM_FIELD_GROUP_DELETE__SUCCESS,
payload: {
customFieldGroup,
},
});
deleteCustomFieldGroup.failure = (id, error) => ({
type: ActionTypes.CUSTOM_FIELD_GROUP_DELETE__FAILURE,
payload: {
id,
error,
},
});
const handleCustomFieldGroupDelete = (customFieldGroup) => ({
type: ActionTypes.CUSTOM_FIELD_GROUP_DELETE_HANDLE,
payload: {
customFieldGroup,
},
});
export default {
createCustomFieldGroup,
handleCustomFieldGroupCreate,
updateCustomFieldGroup,
handleCustomFieldGroupUpdate,
deleteCustomFieldGroup,
handleCustomFieldGroupDelete,
};

View file

@ -0,0 +1,72 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const updateCustomFieldValue = (customFieldValue) => ({
type: ActionTypes.CUSTOM_FIELD_VALUE_UPDATE,
payload: {
customFieldValue,
},
});
updateCustomFieldValue.success = (localId, customFieldValue) => ({
type: ActionTypes.CUSTOM_FIELD_VALUE_UPDATE__SUCCESS,
payload: {
localId,
customFieldValue,
},
});
updateCustomFieldValue.failure = (localId, error) => ({
type: ActionTypes.CUSTOM_FIELD_VALUE_UPDATE__FAILURE,
payload: {
localId,
error,
},
});
const handleCustomFieldValueUpdate = (customFieldValue) => ({
type: ActionTypes.CUSTOM_FIELD_VALUE_UPDATE_HANDLE,
payload: {
customFieldValue,
},
});
const deleteCustomFieldValue = (id) => ({
type: ActionTypes.CUSTOM_FIELD_VALUE_DELETE,
payload: {
id,
},
});
deleteCustomFieldValue.success = (customFieldValue) => ({
type: ActionTypes.CUSTOM_FIELD_VALUE_DELETE__SUCCESS,
payload: {
customFieldValue,
},
});
deleteCustomFieldValue.failure = (id, error) => ({
type: ActionTypes.CUSTOM_FIELD_VALUE_DELETE__FAILURE,
payload: {
id,
error,
},
});
const handleCustomFieldValueDelete = (customFieldValue) => ({
type: ActionTypes.CUSTOM_FIELD_VALUE_DELETE_HANDLE,
payload: {
customFieldValue,
},
});
export default {
updateCustomFieldValue,
handleCustomFieldValueUpdate,
deleteCustomFieldValue,
handleCustomFieldValueDelete,
};

View file

@ -0,0 +1,104 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createCustomField = (customField) => ({
type: ActionTypes.CUSTOM_FIELD_CREATE,
payload: {
customField,
},
});
createCustomField.success = (localId, customField) => ({
type: ActionTypes.CUSTOM_FIELD_CREATE__SUCCESS,
payload: {
localId,
customField,
},
});
createCustomField.failure = (localId, error) => ({
type: ActionTypes.CUSTOM_FIELD_CREATE__FAILURE,
payload: {
localId,
error,
},
});
const handleCustomFieldCreate = (customField) => ({
type: ActionTypes.CUSTOM_FIELD_CREATE_HANDLE,
payload: {
customField,
},
});
const updateCustomField = (id, data) => ({
type: ActionTypes.CUSTOM_FIELD_UPDATE,
payload: {
id,
data,
},
});
updateCustomField.success = (customField) => ({
type: ActionTypes.CUSTOM_FIELD_UPDATE__SUCCESS,
payload: {
customField,
},
});
updateCustomField.failure = (id, error) => ({
type: ActionTypes.CUSTOM_FIELD_UPDATE__FAILURE,
payload: {
id,
error,
},
});
const handleCustomFieldUpdate = (customField) => ({
type: ActionTypes.CUSTOM_FIELD_UPDATE_HANDLE,
payload: {
customField,
},
});
const deleteCustomField = (id) => ({
type: ActionTypes.CUSTOM_FIELD_DELETE,
payload: {
id,
},
});
deleteCustomField.success = (customField) => ({
type: ActionTypes.CUSTOM_FIELD_DELETE__SUCCESS,
payload: {
customField,
},
});
deleteCustomField.failure = (id, error) => ({
type: ActionTypes.CUSTOM_FIELD_DELETE__FAILURE,
payload: {
id,
error,
},
});
const handleCustomFieldDelete = (customField) => ({
type: ActionTypes.CUSTOM_FIELD_DELETE_HANDLE,
payload: {
customField,
},
});
export default {
createCustomField,
handleCustomFieldCreate,
updateCustomField,
handleCustomFieldUpdate,
deleteCustomField,
handleCustomFieldDelete,
};

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import router from './router';
import socket from './socket';
import login from './login';
@ -6,16 +11,23 @@ import modals from './modals';
import users from './users';
import projects from './projects';
import projectManagers from './project-managers';
import backgroundImages from './background-images';
import baseCustomFieldGroups from './base-custom-field-groups';
import boards from './boards';
import boardMemberships from './board-memberships';
import labels from './labels';
import lists from './lists';
import cards from './cards';
import taskLists from './task-lists';
import tasks from './tasks';
import attachments from './attachments';
import customFieldGroups from './custom-field-groups';
import customFields from './custom-fields';
import customFieldValues from './custom-field-values';
import comments from './comments';
import activities from './activities';
import commentActivities from './comment-activities';
import notifications from './notifications';
import notificationServices from './notification-services';
export default {
...router,
@ -26,14 +38,21 @@ export default {
...users,
...projects,
...projectManagers,
...backgroundImages,
...baseCustomFieldGroups,
...boards,
...boardMemberships,
...labels,
...lists,
...cards,
...taskLists,
...tasks,
...attachments,
...customFieldGroups,
...customFields,
...customFieldValues,
...comments,
...activities,
...commentActivities,
...notifications,
...notificationServices,
};

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createLabel = (label) => ({
@ -23,6 +28,32 @@ createLabel.failure = (localId, error) => ({
},
});
const createLabelFromCard = (cardId, label) => ({
type: ActionTypes.LABEL_FROM_CARD_CREATE,
payload: {
cardId,
label,
},
});
createLabelFromCard.success = (localId, label, cardLabel) => ({
type: ActionTypes.LABEL_FROM_CARD_CREATE__SUCCESS,
payload: {
localId,
label,
cardLabel,
},
});
createLabelFromCard.failure = (cardId, localId, error) => ({
type: ActionTypes.LABEL_FROM_CARD_CREATE__FAILURE,
payload: {
cardId,
localId,
error,
},
});
const handleLabelCreate = (label) => ({
type: ActionTypes.LABEL_CREATE_HANDLE,
payload: {
@ -151,24 +182,27 @@ const handleLabelFromCardRemove = (cardLabel) => ({
},
});
const addLabelToBoardFilter = (id, boardId) => ({
const addLabelToBoardFilter = (id, boardId, currentListId) => ({
type: ActionTypes.LABEL_TO_BOARD_FILTER_ADD,
payload: {
id,
boardId,
currentListId,
},
});
const removeLabelFromBoardFilter = (id, boardId) => ({
const removeLabelFromBoardFilter = (id, boardId, currentListId) => ({
type: ActionTypes.LABEL_FROM_BOARD_FILTER_REMOVE,
payload: {
id,
boardId,
currentListId,
},
});
export default {
createLabel,
createLabelFromCard,
handleLabelCreate,
updateLabel,
handleLabelUpdate,

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createList = (list) => ({
@ -84,25 +89,75 @@ sortList.failure = (id, error) => ({
},
});
const handleListSort = (list, cards) => ({
type: ActionTypes.LIST_SORT_HANDLE,
const moveListCards = (id, nextId, cardIds) => ({
type: ActionTypes.LIST_CARDS_MOVE,
payload: {
list,
cards,
id,
nextId,
cardIds,
},
});
const deleteList = (id) => ({
type: ActionTypes.LIST_DELETE,
moveListCards.success = (list, cards, activities) => ({
type: ActionTypes.LIST_CARDS_MOVE__SUCCESS,
payload: {
list,
cards,
activities,
},
});
moveListCards.failure = (id, error) => ({
type: ActionTypes.LIST_CARDS_MOVE__FAILURE,
payload: {
id,
error,
},
});
const clearList = (id) => ({
type: ActionTypes.LIST_CLEAR,
payload: {
id,
},
});
deleteList.success = (list) => ({
clearList.success = (list) => ({
type: ActionTypes.LIST_CLEAR__SUCCESS,
payload: {
list,
},
});
clearList.failure = (id, error) => ({
type: ActionTypes.LIST_CLEAR__FAILURE,
payload: {
id,
error,
},
});
const handleListClear = (list) => ({
type: ActionTypes.LIST_CLEAR_HANDLE,
payload: {
list,
},
});
const deleteList = (id, trashId, cardIds) => ({
type: ActionTypes.LIST_DELETE,
payload: {
id,
trashId,
cardIds,
},
});
deleteList.success = (list, cards) => ({
type: ActionTypes.LIST_DELETE__SUCCESS,
payload: {
list,
cards,
},
});
@ -114,10 +169,11 @@ deleteList.failure = (id, error) => ({
},
});
const handleListDelete = (list) => ({
const handleListDelete = (list, cards) => ({
type: ActionTypes.LIST_DELETE_HANDLE,
payload: {
list,
cards,
},
});
@ -127,7 +183,9 @@ export default {
updateList,
handleListUpdate,
sortList,
handleListSort,
moveListCards,
clearList,
handleListClear,
deleteList,
handleListDelete,
};

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const initializeLogin = (config) => ({
@ -28,20 +33,20 @@ authenticate.failure = (error) => ({
},
});
const authenticateUsingOidc = () => ({
type: ActionTypes.USING_OIDC_AUTHENTICATE,
const authenticateWithOidc = () => ({
type: ActionTypes.WITH_OIDC_AUTHENTICATE,
payload: {},
});
authenticateUsingOidc.success = (accessToken) => ({
type: ActionTypes.USING_OIDC_AUTHENTICATE__SUCCESS,
authenticateWithOidc.success = (accessToken) => ({
type: ActionTypes.WITH_OIDC_AUTHENTICATE__SUCCESS,
payload: {
accessToken,
},
});
authenticateUsingOidc.failure = (error) => ({
type: ActionTypes.USING_OIDC_AUTHENTICATE__FAILURE,
authenticateWithOidc.failure = (error) => ({
type: ActionTypes.WITH_OIDC_AUTHENTICATE__FAILURE,
payload: {
error,
},
@ -55,6 +60,6 @@ const clearAuthenticateError = () => ({
export default {
initializeLogin,
authenticate,
authenticateUsingOidc,
authenticateWithOidc,
clearAuthenticateError,
};

View file

@ -1,9 +1,15 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const openModal = (type) => ({
const openModal = (type, params = {}) => ({
type: ActionTypes.MODAL_OPEN,
payload: {
type,
params,
},
});

View file

@ -0,0 +1,127 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createNotificationService = (notificationService) => ({
type: ActionTypes.NOTIFICATION_SERVICE_CREATE,
payload: {
notificationService,
},
});
createNotificationService.success = (localId, notificationService) => ({
type: ActionTypes.NOTIFICATION_SERVICE_CREATE__SUCCESS,
payload: {
localId,
notificationService,
},
});
createNotificationService.failure = (localId, error) => ({
type: ActionTypes.NOTIFICATION_SERVICE_CREATE__FAILURE,
payload: {
localId,
error,
},
});
const handleNotificationServiceCreate = (notificationService) => ({
type: ActionTypes.NOTIFICATION_SERVICE_CREATE_HANDLE,
payload: {
notificationService,
},
});
const updateNotificationService = (id, data) => ({
type: ActionTypes.NOTIFICATION_SERVICE_UPDATE,
payload: {
id,
data,
},
});
updateNotificationService.success = (notificationService) => ({
type: ActionTypes.NOTIFICATION_SERVICE_UPDATE__SUCCESS,
payload: {
notificationService,
},
});
updateNotificationService.failure = (id, error) => ({
type: ActionTypes.NOTIFICATION_SERVICE_UPDATE__FAILURE,
payload: {
id,
error,
},
});
const handleNotificationServiceUpdate = (notificationService) => ({
type: ActionTypes.NOTIFICATION_SERVICE_UPDATE_HANDLE,
payload: {
notificationService,
},
});
const testNotificationService = (id) => ({
type: ActionTypes.NOTIFICATION_SERVICE_TEST,
payload: {
id,
},
});
testNotificationService.success = (notificationService) => ({
type: ActionTypes.NOTIFICATION_SERVICE_TEST__SUCCESS,
payload: {
notificationService,
},
});
testNotificationService.failure = (id, error) => ({
type: ActionTypes.NOTIFICATION_SERVICE_TEST__FAILURE,
payload: {
id,
error,
},
});
const deleteNotificationService = (id) => ({
type: ActionTypes.NOTIFICATION_SERVICE_DELETE,
payload: {
id,
},
});
deleteNotificationService.success = (notificationService) => ({
type: ActionTypes.NOTIFICATION_SERVICE_DELETE__SUCCESS,
payload: {
notificationService,
},
});
deleteNotificationService.failure = (id, error) => ({
type: ActionTypes.NOTIFICATION_SERVICE_DELETE__FAILURE,
payload: {
id,
error,
},
});
const handleNotificationServiceDelete = (notificationService) => ({
type: ActionTypes.NOTIFICATION_SERVICE_DELETE_HANDLE,
payload: {
notificationService,
},
});
export default {
createNotificationService,
handleNotificationServiceCreate,
updateNotificationService,
handleNotificationServiceUpdate,
testNotificationService,
deleteNotificationService,
handleNotificationServiceDelete,
};

View file

@ -1,12 +1,34 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const handleNotificationCreate = (notification, users, cards, activities) => ({
const deleteAllNotifications = () => ({
type: ActionTypes.ALL_NOTIFICATIONS_DELETE,
payload: {},
});
deleteAllNotifications.success = (notifications) => ({
type: ActionTypes.ALL_NOTIFICATIONS_DELETE__SUCCESS,
payload: {
notifications,
},
});
deleteAllNotifications.failure = (error) => ({
type: ActionTypes.ALL_NOTIFICATIONS_DELETE__FAILURE,
payload: {
error,
},
});
const handleNotificationCreate = (notification, users) => ({
type: ActionTypes.NOTIFICATION_CREATE_HANDLE,
payload: {
notification,
users,
cards,
activities,
},
});
@ -40,6 +62,7 @@ const handleNotificationDelete = (notification) => ({
});
export default {
deleteAllNotifications,
handleNotificationCreate,
deleteNotification,
handleNotificationDelete,

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createProjectManager = (projectManager) => ({
@ -25,10 +30,15 @@ createProjectManager.failure = (localId, error) => ({
const handleProjectManagerCreate = (
projectManager,
boardIds,
isCurrentUser,
isProjectAvailable,
project,
board,
users,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
@ -36,17 +46,27 @@ const handleProjectManagerCreate = (
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
deletedNotifications,
customFieldGroups,
customFields,
customFieldValues,
notificationsToDelete,
notificationServices,
) => ({
type: ActionTypes.PROJECT_MANAGER_CREATE_HANDLE,
payload: {
projectManager,
boardIds,
isCurrentUser,
isProjectAvailable,
project,
board,
users,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
@ -54,27 +74,21 @@ const handleProjectManagerCreate = (
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
deletedNotifications,
customFieldGroups,
customFields,
customFieldValues,
notificationsToDelete,
notificationServices,
},
});
handleProjectManagerCreate.fetchProject = (id, currentUserId, currentBoardId) => ({
type: ActionTypes.PROJECT_MANAGER_CREATE_HANDLE__PROJECT_FETCH,
payload: {
id,
currentUserId,
currentBoardId,
},
});
const deleteProjectManager = (id, isCurrentUser, isCurrentProject) => ({
const deleteProjectManager = (id) => ({
type: ActionTypes.PROJECT_MANAGER_DELETE,
payload: {
id,
isCurrentUser,
isCurrentProject,
},
});
@ -93,12 +107,10 @@ deleteProjectManager.failure = (id, error) => ({
},
});
const handleProjectManagerDelete = (projectManager, isCurrentUser, isCurrentProject) => ({
const handleProjectManagerDelete = (projectManager) => ({
type: ActionTypes.PROJECT_MANAGER_DELETE_HANDLE,
payload: {
projectManager,
isCurrentUser,
isCurrentProject,
},
});

View file

@ -1,5 +1,31 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const searchProjects = (value) => ({
type: ActionTypes.PROJECTS_SEARCH,
payload: {
value,
},
});
const updateProjectsOrder = (value) => ({
type: ActionTypes.PROJECTS_ORDER_UPDATE,
payload: {
value,
},
});
const toggleHiddenProjects = (isVisible) => ({
type: ActionTypes.HIDDEN_PROJECTS_TOGGLE,
payload: {
isVisible,
},
});
const createProject = (data) => ({
type: ActionTypes.PROJECT_CREATE,
payload: {
@ -22,14 +48,28 @@ createProject.failure = (error) => ({
},
});
const handleProjectCreate = (project, users, projectManagers, boards, boardMemberships) => ({
const handleProjectCreate = (
project,
users,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
customFields,
notificationServices,
) => ({
type: ActionTypes.PROJECT_CREATE_HANDLE,
payload: {
project,
users,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
customFields,
notificationServices,
},
});
@ -56,32 +96,56 @@ updateProject.failure = (id, error) => ({
},
});
const handleProjectUpdate = (project) => ({
const handleProjectUpdate = (
project,
boardIds,
isAvailable,
board,
users,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
notificationsToDelete,
notificationServices,
) => ({
type: ActionTypes.PROJECT_UPDATE_HANDLE,
payload: {
project,
},
});
const updateProjectBackgroundImage = (id) => ({
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE,
payload: {
id,
},
});
updateProjectBackgroundImage.success = (project) => ({
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE__SUCCESS,
payload: {
project,
},
});
updateProjectBackgroundImage.failure = (id, error) => ({
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE__FAILURE,
payload: {
id,
error,
boardIds,
isAvailable,
board,
users,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
notificationsToDelete,
notificationServices,
},
});
@ -115,11 +179,13 @@ const handleProjectDelete = (project) => ({
});
export default {
searchProjects,
updateProjectsOrder,
toggleHiddenProjects,
createProject,
handleProjectCreate,
updateProject,
handleProjectUpdate,
updateProjectBackgroundImage,
deleteProject,
handleProjectDelete,
};

View file

@ -1,6 +1,15 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const handleLocationChange = (
pathname,
currentBoardId,
currentCardId,
isEditModeEnabled,
board,
users,
projects,
@ -10,12 +19,20 @@ const handleLocationChange = (
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
deletedNotifications,
customFieldGroups,
customFields,
customFieldValues,
notificationsToDelete,
) => ({
type: ActionTypes.LOCATION_CHANGE_HANDLE,
payload: {
pathname,
currentBoardId,
currentCardId,
isEditModeEnabled,
board,
users,
projects,
@ -25,12 +42,21 @@ const handleLocationChange = (
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
deletedNotifications,
customFieldGroups,
customFields,
customFieldValues,
notificationsToDelete,
},
});
handleLocationChange.fetchContent = () => ({
type: ActionTypes.LOCATION_CHANGE_HANDLE__CONTENT_FETCH,
payload: {},
});
handleLocationChange.fetchBoard = (id) => ({
type: ActionTypes.LOCATION_CHANGE_HANDLE__BOARD_FETCH,
payload: {

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const handleSocketDisconnect = () => ({
@ -6,11 +11,14 @@ const handleSocketDisconnect = () => ({
});
const handleSocketReconnect = (
config,
user,
board,
users,
projects,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
@ -18,18 +26,25 @@ const handleSocketReconnect = (
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
activities,
customFieldGroups,
customFields,
customFieldValues,
notifications,
notificationServices,
) => ({
type: ActionTypes.SOCKET_RECONNECT_HANDLE,
payload: {
config,
user,
board,
users,
projects,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
@ -37,10 +52,14 @@ const handleSocketReconnect = (
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
activities,
customFieldGroups,
customFields,
customFieldValues,
notifications,
notificationServices,
},
});

View file

@ -0,0 +1,104 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createTaskList = (taskList) => ({
type: ActionTypes.TASK_LIST_CREATE,
payload: {
taskList,
},
});
createTaskList.success = (localId, taskList) => ({
type: ActionTypes.TASK_LIST_CREATE__SUCCESS,
payload: {
localId,
taskList,
},
});
createTaskList.failure = (localId, error) => ({
type: ActionTypes.TASK_LIST_CREATE__FAILURE,
payload: {
localId,
error,
},
});
const handleTaskListCreate = (taskList) => ({
type: ActionTypes.TASK_LIST_CREATE_HANDLE,
payload: {
taskList,
},
});
const updateTaskList = (id, data) => ({
type: ActionTypes.TASK_LIST_UPDATE,
payload: {
id,
data,
},
});
updateTaskList.success = (taskList) => ({
type: ActionTypes.TASK_LIST_UPDATE__SUCCESS,
payload: {
taskList,
},
});
updateTaskList.failure = (id, error) => ({
type: ActionTypes.TASK_LIST_UPDATE__FAILURE,
payload: {
id,
error,
},
});
const handleTaskListUpdate = (taskList) => ({
type: ActionTypes.TASK_LIST_UPDATE_HANDLE,
payload: {
taskList,
},
});
const deleteTaskList = (id) => ({
type: ActionTypes.TASK_LIST_DELETE,
payload: {
id,
},
});
deleteTaskList.success = (taskList) => ({
type: ActionTypes.TASK_LIST_DELETE__SUCCESS,
payload: {
taskList,
},
});
deleteTaskList.failure = (id, error) => ({
type: ActionTypes.TASK_LIST_DELETE__FAILURE,
payload: {
id,
error,
},
});
const handleTaskListDelete = (taskList) => ({
type: ActionTypes.TASK_LIST_DELETE_HANDLE,
payload: {
taskList,
},
});
export default {
createTaskList,
handleTaskListCreate,
updateTaskList,
handleTaskListUpdate,
deleteTaskList,
handleTaskListDelete,
};

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createTask = (task) => ({

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import ActionTypes from '../constants/ActionTypes';
const createUser = (data) => ({
@ -56,12 +61,60 @@ updateUser.failure = (id, error) => ({
},
});
const handleUserUpdate = (user, users, isCurrent) => ({
const handleUserUpdate = (
user,
projectIds,
boardIds,
config,
board,
users,
projects,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
notificationsToDelete,
notificationServices,
) => ({
type: ActionTypes.USER_UPDATE_HANDLE,
payload: {
user,
projectIds,
boardIds,
config,
board,
users,
isCurrent,
projects,
projectManagers,
backgroundImages,
baseCustomFieldGroups,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
taskLists,
tasks,
attachments,
customFieldGroups,
customFields,
customFieldValues,
notificationsToDelete,
notificationServices,
},
});
@ -270,19 +323,22 @@ const handleUserFromCardRemove = (cardMembership) => ({
},
});
const addUserToBoardFilter = (id, boardId) => ({
const addUserToBoardFilter = (id, boardId, replace, currentListId) => ({
type: ActionTypes.USER_TO_BOARD_FILTER_ADD,
payload: {
id,
boardId,
replace,
currentListId,
},
});
const removeUserFromBoardFilter = (id, boardId) => ({
const removeUserFromBoardFilter = (id, boardId, currentListId) => ({
type: ActionTypes.USER_FROM_BOARD_FILTER_REMOVE,
payload: {
id,
boardId,
currentListId,
},
});

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import http from './http';
/* Actions */
@ -5,13 +10,13 @@ import http from './http';
const createAccessToken = (data, headers) =>
http.post('/access-tokens?withHttpOnlyToken=true', data, headers);
const exchangeForAccessTokenUsingOidc = (data, headers) =>
http.post('/access-tokens/exchange-using-oidc?withHttpOnlyToken=true', data, headers);
const exchangeForAccessTokenWithOidc = (data, headers) =>
http.post('/access-tokens/exchange-with-oidc?withHttpOnlyToken=true', data, headers);
const deleteCurrentAccessToken = (headers) => http.delete('/access-tokens/me', undefined, headers);
export default {
createAccessToken,
exchangeForAccessTokenUsingOidc,
exchangeForAccessTokenWithOidc,
deleteCurrentAccessToken,
};

View file

@ -1,11 +1,17 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import socket from './socket';
import { transformUser } from './users';
/* Transformers */
export const transformActivity = (activity) => ({
...activity,
createdAt: new Date(activity.createdAt),
...(activity.createdAt && {
createdAt: new Date(activity.createdAt),
}),
});
/* Actions */
@ -14,10 +20,6 @@ const getActivities = (cardId, data, headers) =>
socket.get(`/cards/${cardId}/actions`, data, headers).then((body) => ({
...body,
items: body.items.map(transformActivity),
included: {
...body.included,
users: body.included.users.map(transformUser),
},
}));
/* Event handlers */
@ -29,13 +31,7 @@ const makeHandleActivityCreate = (next) => (body) => {
});
};
const makeHandleActivityUpdate = makeHandleActivityCreate;
const makeHandleActivityDelete = makeHandleActivityCreate;
export default {
getActivities,
makeHandleActivityCreate,
makeHandleActivityUpdate,
makeHandleActivityDelete,
};

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import http from './http';
import socket from './socket';
@ -5,17 +10,34 @@ import socket from './socket';
export const transformAttachment = (attachment) => ({
...attachment,
createdAt: new Date(attachment.createdAt),
...(attachment.createdAt && {
createdAt: new Date(attachment.createdAt),
}),
});
/* Actions */
const createAttachment = (cardId, data, requestId, headers) =>
http.post(`/cards/${cardId}/attachments?requestId=${requestId}`, data, headers).then((body) => ({
const createAttachment = (cardId, data, headers) =>
socket.post(`/cards/${cardId}/attachments`, data, headers).then((body) => ({
...body,
item: transformAttachment(body.item),
}));
const createAttachmentWithFile = (cardId, { file, ...data }, requestId, headers) =>
http
.post(
`/cards/${cardId}/attachments?requestId=${requestId}`,
{
...data,
file,
},
headers,
)
.then((body) => ({
...body,
item: transformAttachment(body.item),
}));
const updateAttachment = (id, data, headers) =>
socket.patch(`/attachments/${id}`, data, headers).then((body) => ({
...body,
@ -43,6 +65,7 @@ const makeHandleAttachmentDelete = makeHandleAttachmentCreate;
export default {
createAttachment,
createAttachmentWithFile,
updateAttachment,
deleteAttachment,
makeHandleAttachmentCreate,

View file

@ -0,0 +1,27 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import http from './http';
import socket from './socket';
/* Actions */
const createBackgroundImage = (projectId, { file, ...data }, requestId, headers) =>
http.post(
`/projects/${projectId}/background-images?requestId=${requestId}`,
{
...data,
file,
},
headers,
);
const deleteBackgroundImage = (id, headers) =>
socket.delete(`/background-images/${id}`, undefined, headers);
export default {
createBackgroundImage,
deleteBackgroundImage,
};

View file

@ -0,0 +1,23 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import socket from './socket';
/* Actions */
const createBaseCustomFieldGroup = (projectId, data, headers) =>
socket.post(`/projects/${projectId}/base-custom-field-groups`, data, headers);
const updateBaseCustomFieldGroup = (id, data, headers) =>
socket.patch(`/base-custom-field-groups/${id}`, data, headers);
const deleteBaseCustomFieldGroup = (id, headers) =>
socket.delete(`/base-custom-field-groups/${id}`, undefined, headers);
export default {
createBaseCustomFieldGroup,
updateBaseCustomFieldGroup,
deleteBaseCustomFieldGroup,
};

View file

@ -1,50 +1,23 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import socket from './socket';
/* Transformers */
export const transformBoardMembership = (boardMembership) => ({
...boardMembership,
createdAt: new Date(boardMembership.createdAt),
});
/* Actions */
const createBoardMembership = (boardId, data, headers) =>
socket.post(`/boards/${boardId}/memberships`, data, headers).then((body) => ({
...body,
item: transformBoardMembership(body.item),
}));
socket.post(`/boards/${boardId}/board-memberships`, data, headers);
const updateBoardMembership = (id, data, headers) =>
socket.patch(`/board-memberships/${id}`, data, headers).then((body) => ({
...body,
item: transformBoardMembership(body.item),
}));
socket.patch(`/board-memberships/${id}`, data, headers);
const deleteBoardMembership = (id, headers) =>
socket.delete(`/board-memberships/${id}`, undefined, headers).then((body) => ({
...body,
item: transformBoardMembership(body.item),
}));
/* Event handlers */
const makeHandleBoardMembershipCreate = (next) => (body) => {
next({
...body,
item: transformBoardMembership(body.item),
});
};
const makeHandleBoardMembershipUpdate = makeHandleBoardMembershipCreate;
const makeHandleBoardMembershipDelete = makeHandleBoardMembershipCreate;
socket.delete(`/board-memberships/${id}`, undefined, headers);
export default {
createBoardMembership,
updateBoardMembership,
deleteBoardMembership,
makeHandleBoardMembershipCreate,
makeHandleBoardMembershipUpdate,
makeHandleBoardMembershipDelete,
};

View file

@ -1,20 +1,17 @@
import socket from './socket';
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import http from './http';
import { transformUser } from './users';
import { transformBoardMembership } from './board-memberships';
import socket from './socket';
import { transformCard } from './cards';
import { transformAttachment } from './attachments';
/* Actions */
const createBoard = (projectId, data, headers) =>
socket.post(`/projects/${projectId}/boards`, data, headers).then((body) => ({
...body,
included: {
...body.included,
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
},
}));
socket.post(`/projects/${projectId}/boards`, data, headers);
const createBoardWithImport = (projectId, data, requestId, headers) =>
http.post(`/projects/${projectId}/boards?requestId=${requestId}`, data, headers);
@ -26,8 +23,6 @@ const getBoard = (id, subscribe, headers) =>
...body,
included: {
...body.included,
users: body.included.users.map(transformUser),
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
cards: body.included.cards.map(transformCard),
attachments: body.included.attachments.map(transformAttachment),
},

View file

@ -1,12 +1,17 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import socket from './socket';
/* Actions */
const createCardLabel = (cardId, data, headers) =>
socket.post(`/cards/${cardId}/labels`, data, headers);
socket.post(`/cards/${cardId}/card-labels`, data, headers);
const deleteCardLabel = (cardId, labelId, headers) =>
socket.delete(`/cards/${cardId}/labels/${labelId}`, undefined, headers);
socket.delete(`/cards/${cardId}/card-labels/labelId:${labelId}`, undefined, headers);
export default {
createCardLabel,

View file

@ -1,12 +1,17 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import socket from './socket';
/* Actions */
const createCardMembership = (cardId, data, headers) =>
socket.post(`/cards/${cardId}/memberships`, data, headers);
socket.post(`/cards/${cardId}/card-memberships`, data, headers);
const deleteCardMembership = (cardId, userId, headers) =>
socket.delete(`/cards/${cardId}/memberships?userId=${userId}`, undefined, headers);
socket.delete(`/cards/${cardId}/card-memberships/userId:${userId}`, undefined, headers);
export default {
createCardMembership,

View file

@ -1,5 +1,14 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import omit from 'lodash/omit';
import socket from './socket';
import { transformAttachment } from './attachments';
import { transformActivity } from './activities';
import { transformNotification } from './notifications';
/* Transformers */
@ -16,6 +25,12 @@ export const transformCard = (card) => ({
}),
},
}),
...(card.createdAt && {
createdAt: new Date(card.createdAt),
}),
...(card.listChangedAt && {
listChangedAt: new Date(card.listChangedAt),
}),
});
export const transformCardData = (data) => ({
@ -35,6 +50,16 @@ export const transformCardData = (data) => ({
/* Actions */
const getCards = (listId, data, headers) =>
socket.get(`/lists/${listId}/cards`, data, headers).then((body) => ({
...body,
items: body.items.map(transformCard),
included: {
...body.included,
attachments: body.included.attachments.map(transformAttachment),
},
}));
const createCard = (listId, data, headers) =>
socket.post(`/lists/${listId}/cards`, transformCardData(data), headers).then((body) => ({
...body,
@ -61,6 +86,20 @@ const duplicateCard = (id, data, headers) =>
socket.post(`/cards/${id}/duplicate`, data, headers).then((body) => ({
...body,
item: transformCard(body.item),
included: {
...body.included,
attachments: body.included.attachments.map(transformAttachment),
},
}));
const readCardNotifications = (id, headers) =>
socket.post(`/cards/${id}/read-notifications`, undefined, headers).then((body) => ({
...body,
item: transformCard(body.item),
included: {
...body.included,
notifications: body.included.notifications.map(transformNotification),
},
}));
const deleteCard = (id, headers) =>
@ -71,6 +110,17 @@ const deleteCard = (id, headers) =>
/* Event handlers */
const makeHandleCardsUpdate = (next) => (body) => {
next({
...body,
items: body.items.map(transformCard),
included: body.included && {
...omit(body.included, 'actions'),
activities: body.included.actions.map(transformActivity),
},
});
};
const makeHandleCardCreate = (next) => (body) => {
next({
...body,
@ -80,14 +130,17 @@ const makeHandleCardCreate = (next) => (body) => {
const makeHandleCardUpdate = makeHandleCardCreate;
const makeHandleCardDelete = makeHandleCardCreate;
const makeHandleCardDelete = makeHandleCardUpdate;
export default {
getCards,
createCard,
getCard,
updateCard,
deleteCard,
duplicateCard,
readCardNotifications,
deleteCard,
makeHandleCardsUpdate,
makeHandleCardCreate,
makeHandleCardUpdate,
makeHandleCardDelete,

View file

@ -1,28 +0,0 @@
import socket from './socket';
import { transformActivity } from './activities';
/* Actions */
const createCommentActivity = (cardId, data, headers) =>
socket.post(`/cards/${cardId}/comment-actions`, data, headers).then((body) => ({
...body,
item: transformActivity(body.item),
}));
const updateCommentActivity = (id, data, headers) =>
socket.patch(`/comment-actions/${id}`, data, headers).then((body) => ({
...body,
item: transformActivity(body.item),
}));
const deleteCommentActivity = (id, headers) =>
socket.delete(`/comment-actions/${id}`, undefined, headers).then((body) => ({
...body,
item: transformActivity(body.item),
}));
export default {
createCommentActivity,
updateCommentActivity,
deleteCommentActivity,
};

View file

@ -0,0 +1,64 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import socket from './socket';
/* Transformers */
export const transformComment = (comment) => ({
...comment,
...(comment.createdAt && {
createdAt: new Date(comment.createdAt),
}),
});
/* Actions */
const getComments = (cardId, data, headers) =>
socket.get(`/cards/${cardId}/comments`, data, headers).then((body) => ({
...body,
items: body.items.map(transformComment),
}));
const createComment = (cardId, data, headers) =>
socket.post(`/cards/${cardId}/comments`, data, headers).then((body) => ({
...body,
item: transformComment(body.item),
}));
const updateComment = (id, data, headers) =>
socket.patch(`/comments/${id}`, data, headers).then((body) => ({
...body,
item: transformComment(body.item),
}));
const deleteComment = (id, headers) =>
socket.delete(`/comments/${id}`, undefined, headers).then((body) => ({
...body,
item: transformComment(body.item),
}));
/* Event handlers */
const makeHandleCommentCreate = (next) => (body) => {
next({
...body,
item: transformComment(body.item),
});
};
const makeHandleCommentUpdate = makeHandleCommentCreate;
const makeHandleCommentDelete = makeHandleCommentUpdate;
export default {
getComments,
createComment,
updateComment,
deleteComment,
makeHandleCommentCreate,
makeHandleCommentUpdate,
makeHandleCommentDelete,
};

14
client/src/api/config.js Normal file
View file

@ -0,0 +1,14 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import http from './http';
/* Actions */
const getConfig = (headers) => http.get('/config', undefined, headers);
export default {
getConfig,
};

View file

@ -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 socket from './socket';
/* Actions */
const createCustomFieldGroupInBoard = (cardId, data, headers) =>
socket.post(`/boards/${cardId}/custom-field-groups`, data, headers);
const createCustomFieldGroupInCard = (cardId, data, headers) =>
socket.post(`/cards/${cardId}/custom-field-groups`, data, headers);
const getCustomFieldGroup = (id, headers) =>
socket.get(`/custom-field-groups/${id}`, undefined, headers);
const updateCustomFieldGroup = (id, data, headers) =>
socket.patch(`/custom-field-groups/${id}`, data, headers);
const deleteCustomFieldGroup = (id, headers) =>
socket.delete(`/custom-field-groups/${id}`, undefined, headers);
export default {
createCustomFieldGroupInBoard,
createCustomFieldGroupInCard,
getCustomFieldGroup,
updateCustomFieldGroup,
deleteCustomFieldGroup,
};

View file

@ -0,0 +1,27 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import socket from './socket';
/* Actions */
const updateCustomFieldValue = (cardId, customFieldGroupId, customFieldId, data, headers) =>
socket.patch(
`/cards/${cardId}/custom-field-values/customFieldGroupId:${customFieldGroupId}:customFieldId:${customFieldId}`,
data,
headers,
);
const deleteCustomFieldValue = (cardId, customFieldGroupId, customFieldId, headers) =>
socket.delete(
`/cards/${cardId}/custom-field-values/customFieldGroupId:${customFieldGroupId}:customFieldId:${customFieldId}`,
undefined,
headers,
);
export default {
updateCustomFieldValue,
deleteCustomFieldValue,
};

27
client/src/api/custom-fields.js Executable file
View file

@ -0,0 +1,27 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import socket from './socket';
/* Actions */
const createCustomFieldInBaseGroup = (baseCustomFieldGroupId, data, headers) =>
socket.post(`/base-custom-field-groups/${baseCustomFieldGroupId}/custom-fields`, data, headers);
const createCustomFieldInGroup = (customFieldGroupId, data, headers) =>
socket.post(`/custom-field-groups/${customFieldGroupId}/custom-fields`, data, headers);
const updateCustomField = (id, data, headers) =>
socket.patch(`/custom-fields/${id}`, data, headers);
const deleteCustomField = (id, headers) =>
socket.delete(`/custom-fields/${id}`, undefined, headers);
export default {
createCustomFieldInBaseGroup,
createCustomFieldInGroup,
updateCustomField,
deleteCustomField,
};

View file

@ -1,4 +1,7 @@
import { fetch } from 'whatwg-fetch';
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import Config from '../constants/Config';

View file

@ -1,10 +1,17 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import http from './http';
import socket from './socket';
import root from './root';
import config from './config';
import accessTokens from './access-tokens';
import users from './users';
import projects from './projects';
import projectManagers from './project-managers';
import backgroundImages from './background-images';
import baseCustomFieldGroups from './base-custom-field-groups';
import boards from './boards';
import boardMemberships from './board-memberships';
import labels from './labels';
@ -12,20 +19,27 @@ import lists from './lists';
import cards from './cards';
import cardMemberships from './card-memberships';
import cardLabels from './card-labels';
import taskLists from './task-lists';
import tasks from './tasks';
import attachments from './attachments';
import customFieldGroups from './custom-field-groups';
import customFields from './custom-fields';
import customFieldValues from './custom-field-values';
import comments from './comments';
import activities from './activities';
import commentActivities from './comment-activities';
import notifications from './notifications';
import notificationServices from './notification-services';
export { http, socket };
export default {
...root,
...config,
...accessTokens,
...users,
...projects,
...projectManagers,
...backgroundImages,
...baseCustomFieldGroups,
...boards,
...boardMemberships,
...labels,
@ -33,9 +47,14 @@ export default {
...cards,
...cardMemberships,
...cardLabels,
...taskLists,
...tasks,
...attachments,
...customFieldGroups,
...customFields,
...customFieldValues,
...comments,
...activities,
...commentActivities,
...notifications,
...notificationServices,
};

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import socket from './socket';
/* Actions */

View file

@ -1,11 +1,30 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import omit from 'lodash/omit';
import socket from './socket';
import { transformCard } from './cards';
import { transformAttachment } from './attachments';
import { transformActivity } from './activities';
/* Actions */
const createList = (boardId, data, headers) =>
socket.post(`/boards/${boardId}/lists`, data, headers);
const getList = (id, headers) =>
socket.get(`/lists/${id}`, undefined, headers).then((body) => ({
...body,
included: {
...body.included,
cards: body.included.cards.map(transformCard),
attachments: body.included.attachments.map(transformAttachment),
},
}));
const updateList = (id, data, headers) => socket.patch(`/lists/${id}`, data, headers);
const sortList = (id, data, headers) =>
@ -17,11 +36,30 @@ const sortList = (id, data, headers) =>
},
}));
const deleteList = (id, headers) => socket.delete(`/lists/${id}`, undefined, headers);
const moveListCards = (id, data, headers) =>
socket.post(`/lists/${id}/move-cards`, data, headers).then((body) => ({
...body,
included: {
...omit(body.included, 'actions'),
cards: body.included.cards.map(transformCard),
activities: body.included.actions.map(transformActivity),
},
}));
const clearList = (id, headers) => socket.post(`/lists/${id}/clear`, undefined, headers);
const deleteList = (id, headers) =>
socket.delete(`/lists/${id}`, undefined, headers).then((body) => ({
...body,
included: {
...body.included,
cards: body.included.cards.map(transformCard),
},
}));
/* Event handlers */
const makeHandleListSort = (next) => (body) => {
const makeHandleListDelete = (next) => (body) => {
next({
...body,
included: {
@ -33,8 +71,11 @@ const makeHandleListSort = (next) => (body) => {
export default {
createList,
getList,
updateList,
sortList,
moveListCards,
clearList,
deleteList,
makeHandleListSort,
makeHandleListDelete,
};

View file

@ -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 socket from './socket';
/* Actions */
const createNotificationServiceInUser = (userId, data, headers) =>
socket.post(`/users/${userId}/notification-services`, data, headers);
const createNotificationServiceInBoard = (boardId, data, headers) =>
socket.post(`/boards/${boardId}/notification-services`, data, headers);
const updateNotificationService = (id, data, headers) =>
socket.patch(`/notification-services/${id}`, data, headers);
const testNotificationService = (id, headers) =>
socket.post(`/notification-services/${id}/test`, undefined, headers);
const deleteNotificationService = (id, headers) =>
socket.delete(`/notification-services/${id}`, undefined, headers);
export default {
createNotificationServiceInUser,
createNotificationServiceInBoard,
updateNotificationService,
testNotificationService,
deleteNotificationService,
};

View file

@ -1,15 +1,21 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import omit from 'lodash/omit';
import socket from './socket';
import { transformUser } from './users';
import { transformCard } from './cards';
import { transformActivity } from './activities';
/* Transformers */
export const transformNotification = (notification) => ({
...omit(notification, 'actionId'),
activityId: notification.actionId,
...(notification.actionId
? {
...omit(notification, 'actionId'),
activityId: notification.actionId,
}
: notification),
});
/* Actions */
@ -18,28 +24,25 @@ const getNotifications = (headers) =>
socket.get('/notifications', undefined, headers).then((body) => ({
...body,
items: body.items.map(transformNotification),
included: {
...omit(body.included, 'actions'),
users: body.included.users.map(transformUser),
cards: body.included.cards.map(transformCard),
activities: body.included.actions.map(transformActivity),
},
}));
const getNotification = (id, headers) =>
/* const getNotification = (id, headers) =>
socket.get(`/notifications/${id}`, undefined, headers).then((body) => ({
...body,
item: transformNotification(body.item),
included: {
...omit(body.included, 'actions'),
users: body.included.users.map(transformUser),
cards: body.included.cards.map(transformCard),
activities: body.included.actions.map(transformActivity),
},
})); */
const updateNotification = (id, data, headers) =>
socket.patch(`/notifications/${id}`, data, headers).then((body) => ({
...body,
item: transformNotification(body.item),
}));
const updateNotifications = (ids, data, headers) =>
socket.patch(`/notifications/${ids.join(',')}`, data, headers).then((body) => ({
const readAllNotifications = (headers) =>
socket.post('/notifications/read-all', undefined, headers).then((body) => ({
...body,
items: body.items.map(transformNotification),
}));
@ -57,8 +60,9 @@ const makeHandleNotificationUpdate = makeHandleNotificationCreate;
export default {
getNotifications,
getNotification,
updateNotifications,
// getNotification,
updateNotification,
readAllNotifications,
makeHandleNotificationCreate,
makeHandleNotificationUpdate,
};

View file

@ -1,40 +1,19 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import socket from './socket';
/* Transformers */
export const transformProjectManager = (projectManager) => ({
...projectManager,
createdAt: new Date(projectManager.createdAt),
});
/* Actions */
const createProjectManager = (projectId, data, headers) =>
socket.post(`/projects/${projectId}/managers`, data, headers).then((body) => ({
...body,
item: transformProjectManager(body.item),
}));
socket.post(`/projects/${projectId}/project-managers`, data, headers);
const deleteProjectManager = (id, headers) =>
socket.delete(`/project-managers/${id}`, undefined, headers).then((body) => ({
...body,
item: transformProjectManager(body.item),
}));
/* Event handlers */
const makeHandleProjectManagerCreate = (next) => (body) => {
next({
...body,
item: transformProjectManager(body.item),
});
};
const makeHandleProjectManagerDelete = makeHandleProjectManagerCreate;
socket.delete(`/project-managers/${id}`, undefined, headers);
export default {
createProjectManager,
deleteProjectManager,
makeHandleProjectManagerCreate,
makeHandleProjectManagerDelete,
};

View file

@ -1,47 +1,20 @@
import http from './http';
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import socket from './socket';
import { transformUser } from './users';
import { transformProjectManager } from './project-managers';
import { transformBoardMembership } from './board-memberships';
/* Actions */
const getProjects = (headers) =>
socket.get('/projects', undefined, headers).then((body) => ({
...body,
included: {
...body.included,
users: body.included.users.map(transformUser),
projectManagers: body.included.projectManagers.map(transformProjectManager),
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
},
}));
const getProjects = (headers) => socket.get('/projects', undefined, headers);
const createProject = (data, headers) =>
socket.post('/projects', data, headers).then((body) => ({
...body,
included: {
...body.included,
projectManagers: body.included.projectManagers.map(transformProjectManager),
},
}));
const createProject = (data, headers) => socket.post('/projects', data, headers);
const getProject = (id, headers) =>
socket.get(`/projects/${id}`, undefined, headers).then((body) => ({
...body,
included: {
...body.included,
users: body.included.users.map(transformUser),
projectManagers: body.included.projectManagers.map(transformProjectManager),
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
},
}));
const getProject = (id, headers) => socket.get(`/projects/${id}`, undefined, headers);
const updateProject = (id, data, headers) => socket.patch(`/projects/${id}`, data, headers);
const updateProjectBackgroundImage = (id, data, headers) =>
http.post(`/projects/${id}/background-image`, data, headers);
const deleteProject = (id, headers) => socket.delete(`/projects/${id}`, undefined, headers);
export default {
@ -49,6 +22,5 @@ export default {
createProject,
getProject,
updateProject,
updateProjectBackgroundImage,
deleteProject,
};

View file

@ -1,9 +0,0 @@
import http from './http';
/* Actions */
const getConfig = (headers) => http.get('/config', undefined, headers);
export default {
getConfig,
};

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import socketIOClient from 'socket.io-client';
import sailsIOClient from 'sails.io.js';
@ -5,15 +10,14 @@ import Config from '../constants/Config';
const io = sailsIOClient(socketIOClient);
io.sails.url = Config.SERVER_HOST_NAME;
io.sails.url = Config.SERVER_BASE_URL;
io.sails.autoConnect = false;
io.sails.reconnection = true;
io.sails.useCORSRouteToGetCookie = false;
io.sails.environment = process.env.NODE_ENV;
io.sails.environment = import.meta.env.MODE;
const { socket } = io;
socket.path = `${Config.SERVER_BASE_PATH}/socket.io`;
socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle
['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => {

View file

@ -0,0 +1,24 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import socket from './socket';
/* Actions */
const createTaskList = (cardId, data, headers) =>
socket.post(`/cards/${cardId}/task-lists`, data, headers);
const getTaskList = (id, headers) => socket.get(`/task-lists/${id}`, undefined, headers);
const updateTaskList = (id, data, headers) => socket.patch(`/task-lists/${id}`, data, headers);
const deleteTaskList = (id, headers) => socket.delete(`/task-lists/${id}`, undefined, headers);
export default {
createTaskList,
getTaskList,
updateTaskList,
deleteTaskList,
};

View file

@ -1,8 +1,14 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import socket from './socket';
/* Actions */
const createTask = (cardId, data, headers) => socket.post(`/cards/${cardId}/tasks`, data, headers);
const createTask = (taskListId, data, headers) =>
socket.post(`/task-lists/${taskListId}/tasks`, data, headers);
const updateTask = (id, data, headers) => socket.patch(`/tasks/${id}`, data, headers);

View file

@ -1,92 +1,44 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import http from './http';
import socket from './socket';
/* Transformers */
export const transformUser = (user) => ({
...user,
createdAt: new Date(user.createdAt),
});
/* Actions */
const getUsers = (headers) =>
socket.get('/users', undefined, headers).then((body) => ({
...body,
items: body.items.map(transformUser),
}));
const getUsers = (headers) => socket.get('/users', undefined, headers);
const createUser = (data, headers) =>
socket.post('/users', data, headers).then((body) => ({
...body,
item: transformUser(body.item),
}));
const createUser = (data, headers) => socket.post('/users', data, headers);
const getUser = (id, headers) =>
/* const getUser = (id, headers) =>
socket.get(`/users/${id}`, undefined, headers).then((body) => ({
...body,
item: transformUser(body.item),
}));
})); */
const getCurrentUser = (subscribe, headers) =>
socket.get(`/users/me${subscribe ? '?subscribe=true' : ''}`, undefined, headers).then((body) => ({
...body,
item: transformUser(body.item),
}));
socket.get(`/users/me${subscribe ? '?subscribe=true' : ''}`, undefined, headers);
const updateUser = (id, data, headers) =>
socket.patch(`/users/${id}`, data, headers).then((body) => ({
...body,
item: transformUser(body.item),
}));
const updateUser = (id, data, headers) => socket.patch(`/users/${id}`, data, headers);
const updateUserEmail = (id, data, headers) =>
socket.patch(`/users/${id}/email`, data, headers).then((body) => ({
...body,
item: transformUser(body.item),
}));
const updateUserEmail = (id, data, headers) => socket.patch(`/users/${id}/email`, data, headers);
const updateUserPassword = (id, data, headers) =>
socket.patch(`/users/${id}/password`, data, headers).then((body) => ({
...body,
item: transformUser(body.item),
}));
socket.patch(`/users/${id}/password`, data, headers);
const updateUserUsername = (id, data, headers) =>
socket.patch(`/users/${id}/username`, data, headers).then((body) => ({
...body,
item: transformUser(body.item),
}));
socket.patch(`/users/${id}/username`, data, headers);
const updateUserAvatar = (id, data, headers) =>
http.post(`/users/${id}/avatar`, data, headers).then((body) => ({
...body,
item: transformUser(body.item),
}));
const updateUserAvatar = (id, data, headers) => http.post(`/users/${id}/avatar`, data, headers);
const deleteUser = (id, headers) =>
socket.delete(`/users/${id}`, undefined, headers).then((body) => ({
...body,
item: transformUser(body.item),
}));
/* Event handlers */
const makeHandleUserCreate = (next) => (body) => {
next({
...body,
item: transformUser(body.item),
});
};
const makeHandleUserUpdate = makeHandleUserCreate;
const makeHandleUserDelete = makeHandleUserCreate;
const deleteUser = (id, headers) => socket.delete(`/users/${id}`, undefined, headers);
export default {
getUsers,
createUser,
getUser,
// getUser,
getCurrentUser,
updateUser,
updateUserEmail,
@ -94,7 +46,4 @@ export default {
updateUserUsername,
updateUserAvatar,
deleteUser,
makeHandleUserCreate,
makeHandleUserUpdate,
makeHandleUserDelete,
};

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 559 KiB

After

Width:  |  Height:  |  Size: 157 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before After
Before After

View file

@ -1,38 +0,0 @@
import upperFirst from 'lodash/upperFirst';
import camelCase from 'lodash/camelCase';
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { ProjectBackgroundTypes } from '../../constants/Enums';
import styles from './Background.module.scss';
import globalStyles from '../../styles.module.scss';
function Background({ type, name, imageUrl }) {
return (
<div
className={classNames(
styles.wrapper,
type === ProjectBackgroundTypes.GRADIENT &&
globalStyles[`background${upperFirst(camelCase(name))}`],
)}
style={{
background: type === 'image' && `url("${imageUrl}") center / cover`,
}}
/>
);
}
Background.propTypes = {
type: PropTypes.string.isRequired,
name: PropTypes.string,
imageUrl: PropTypes.string,
};
Background.defaultProps = {
name: undefined,
imageUrl: undefined,
};
export default Background;

View file

@ -1,10 +0,0 @@
:global(#app) {
.wrapper {
height: 100%;
max-height: 100vh;
max-width: 100vw;
position: fixed;
width: 100%;
z-index: -1;
}
}

View file

@ -1,3 +0,0 @@
import Background from './Background';
export default Background;

View file

@ -1,201 +0,0 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import { closePopup } from '../../lib/popup';
import DroppableTypes from '../../constants/DroppableTypes';
import ListContainer from '../../containers/ListContainer';
import CardModalContainer from '../../containers/CardModalContainer';
import ListAdd from './ListAdd';
import { ReactComponent as PlusMathIcon } from '../../assets/images/plus-math-icon.svg';
import styles from './Board.module.scss';
import globalStyles from '../../styles.module.scss';
const parseDndId = (dndId) => dndId.split(':')[1];
const Board = React.memo(
({ listIds, isCardModalOpened, canEdit, onListCreate, onListMove, onCardMove }) => {
const [t] = useTranslation();
const [isListAddOpened, setIsListAddOpened] = useState(false);
const wrapper = useRef(null);
const prevPosition = useRef(null);
const handleAddListClick = useCallback(() => {
setIsListAddOpened(true);
}, []);
const handleAddListClose = useCallback(() => {
setIsListAddOpened(false);
}, []);
const handleDragStart = useCallback(() => {
document.body.classList.add(globalStyles.dragging);
closePopup();
}, []);
const handleDragEnd = useCallback(
({ draggableId, type, source, destination }) => {
document.body.classList.remove(globalStyles.dragging);
if (
!destination ||
(source.droppableId === destination.droppableId && source.index === destination.index)
) {
return;
}
const id = parseDndId(draggableId);
switch (type) {
case DroppableTypes.LIST:
onListMove(id, destination.index);
break;
case DroppableTypes.CARD:
onCardMove(id, parseDndId(destination.droppableId), destination.index);
break;
default:
}
},
[onListMove, onCardMove],
);
const handleMouseDown = useCallback(
(event) => {
// If button is defined and not equal to 0 (left click)
if (event.button) {
return;
}
if (event.target !== wrapper.current && !event.target.dataset.dragScroller) {
return;
}
prevPosition.current = event.clientX;
window.getSelection().removeAllRanges();
document.body.classList.add(globalStyles.dragScrolling);
},
[wrapper],
);
const handleWindowMouseMove = useCallback(
(event) => {
if (prevPosition.current === null) {
return;
}
event.preventDefault();
window.scrollBy({
left: prevPosition.current - event.clientX,
});
prevPosition.current = event.clientX;
},
[prevPosition],
);
const handleWindowMouseRelease = useCallback(() => {
if (prevPosition.current === null) {
return;
}
prevPosition.current = null;
document.body.classList.remove(globalStyles.dragScrolling);
}, [prevPosition]);
useEffect(() => {
document.body.style.overflowX = 'auto';
return () => {
document.body.style.overflowX = null;
};
}, []);
useEffect(() => {
if (isListAddOpened) {
window.scroll(document.body.scrollWidth, 0);
}
}, [listIds, isListAddOpened]);
useEffect(() => {
window.addEventListener('mousemove', handleWindowMouseMove);
window.addEventListener('mouseup', handleWindowMouseRelease);
window.addEventListener('blur', handleWindowMouseRelease);
window.addEventListener('contextmenu', handleWindowMouseRelease);
return () => {
window.removeEventListener('mousemove', handleWindowMouseMove);
window.removeEventListener('mouseup', handleWindowMouseRelease);
window.removeEventListener('blur', handleWindowMouseRelease);
window.removeEventListener('contextmenu', handleWindowMouseRelease);
};
}, [handleWindowMouseMove, handleWindowMouseRelease]);
return (
<>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div ref={wrapper} className={styles.wrapper} onMouseDown={handleMouseDown}>
<div>
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
<Droppable droppableId="board" type={DroppableTypes.LIST} direction="horizontal">
{({ innerRef, droppableProps, placeholder }) => (
<div
{...droppableProps} // eslint-disable-line react/jsx-props-no-spreading
data-drag-scroller
ref={innerRef}
className={styles.lists}
>
{listIds.map((listId, index) => (
<ListContainer key={listId} id={listId} index={index} />
))}
{placeholder}
{canEdit && (
<div data-drag-scroller className={styles.list}>
{isListAddOpened ? (
<ListAdd onCreate={onListCreate} onClose={handleAddListClose} />
) : (
<button
type="button"
className={styles.addListButton}
onClick={handleAddListClick}
>
<PlusMathIcon className={styles.addListButtonIcon} />
<span className={styles.addListButtonText}>
{listIds.length > 0
? t('action.addAnotherList')
: t('action.addList')}
</span>
</button>
)}
</div>
)}
</div>
)}
</Droppable>
</DragDropContext>
</div>
</div>
{isCardModalOpened && <CardModalContainer />}
</>
);
},
);
Board.propTypes = {
listIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
isCardModalOpened: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
onListCreate: PropTypes.func.isRequired,
onListMove: PropTypes.func.isRequired,
onCardMove: PropTypes.func.isRequired,
};
export default Board;

View file

@ -1,89 +0,0 @@
import React, { useCallback, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Form, Input } from 'semantic-ui-react';
import { useDidUpdate, useToggle } from '../../lib/hooks';
import { useClosableForm, useForm } from '../../hooks';
import styles from './ListAdd.module.scss';
const DEFAULT_DATA = {
name: '',
};
const ListAdd = React.memo(({ onCreate, onClose }) => {
const [t] = useTranslation();
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
const [focusNameFieldState, focusNameField] = useToggle();
const nameField = useRef(null);
const handleFieldKeyDown = useCallback(
(event) => {
if (event.key === 'Escape') {
onClose();
}
},
[onClose],
);
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(onClose);
const handleSubmit = useCallback(() => {
const cleanData = {
...data,
name: data.name.trim(),
};
if (!cleanData.name) {
nameField.current.select();
return;
}
onCreate(cleanData);
setData(DEFAULT_DATA);
focusNameField();
}, [onCreate, data, setData, focusNameField]);
useEffect(() => {
nameField.current.focus();
}, []);
useDidUpdate(() => {
nameField.current.focus();
}, [focusNameFieldState]);
return (
<Form className={styles.wrapper} onSubmit={handleSubmit}>
<Input
ref={nameField}
name="name"
value={data.name}
placeholder={t('common.enterListTitle')}
className={styles.field}
onKeyDown={handleFieldKeyDown}
onChange={handleFieldChange}
onBlur={handleFieldBlur}
/>
<div className={styles.controls}>
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
<Button
positive
content={t('action.addList')}
className={styles.button}
onMouseOver={handleControlMouseOver}
onMouseOut={handleControlMouseOut}
/>
</div>
</Form>
);
});
ListAdd.propTypes = {
onCreate: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
export default ListAdd;

View file

@ -1,33 +0,0 @@
:global(#app) {
.button {
min-height: 30px;
vertical-align: top;
}
.controls {
margin-top: 4px;
}
.field {
border: none;
border-radius: 3px;
box-shadow: 0 1px 0 #ccc;
color: #333;
outline: none;
overflow: hidden;
width: 100%;
&:focus {
border-color: #298fca;
box-shadow: 0 0 2px #298fca;
}
}
.wrapper {
background: #e2e4e6;
border-radius: 3px;
padding: 4px;
transition: opacity 40ms ease-in;
width: 272px;
}
}

View file

@ -1,3 +0,0 @@
import Board from './Board';
export default Board;

View file

@ -1,97 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import Filters from './Filters';
import Memberships from '../Memberships';
import BoardMembershipPermissionsSelectStep from '../BoardMembershipPermissionsSelectStep';
import styles from './BoardActions.module.scss';
const BoardActions = React.memo(
({
memberships,
labels,
filterUsers,
filterLabels,
filterText,
allUsers,
canEdit,
canEditMemberships,
onMembershipCreate,
onMembershipUpdate,
onMembershipDelete,
onUserToFilterAdd,
onUserFromFilterRemove,
onLabelToFilterAdd,
onLabelFromFilterRemove,
onLabelCreate,
onLabelUpdate,
onLabelMove,
onLabelDelete,
onTextFilterUpdate,
}) => {
return (
<div className={styles.wrapper}>
<div className={styles.actions}>
<div className={styles.action}>
<Memberships
items={memberships}
allUsers={allUsers}
permissionsSelectStep={BoardMembershipPermissionsSelectStep}
canEdit={canEditMemberships}
onCreate={onMembershipCreate}
onUpdate={onMembershipUpdate}
onDelete={onMembershipDelete}
/>
</div>
<div className={styles.action}>
<Filters
users={filterUsers}
labels={filterLabels}
filterText={filterText}
allBoardMemberships={memberships}
allLabels={labels}
canEdit={canEdit}
onUserAdd={onUserToFilterAdd}
onUserRemove={onUserFromFilterRemove}
onLabelAdd={onLabelToFilterAdd}
onLabelRemove={onLabelFromFilterRemove}
onLabelCreate={onLabelCreate}
onLabelUpdate={onLabelUpdate}
onLabelMove={onLabelMove}
onLabelDelete={onLabelDelete}
onTextFilterUpdate={onTextFilterUpdate}
/>
</div>
</div>
</div>
);
},
);
BoardActions.propTypes = {
/* eslint-disable react/forbid-prop-types */
memberships: PropTypes.array.isRequired,
labels: PropTypes.array.isRequired,
filterUsers: PropTypes.array.isRequired,
filterLabels: PropTypes.array.isRequired,
filterText: PropTypes.string.isRequired,
allUsers: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */
canEdit: PropTypes.bool.isRequired,
canEditMemberships: PropTypes.bool.isRequired,
onMembershipCreate: PropTypes.func.isRequired,
onMembershipUpdate: PropTypes.func.isRequired,
onMembershipDelete: PropTypes.func.isRequired,
onUserToFilterAdd: PropTypes.func.isRequired,
onUserFromFilterRemove: PropTypes.func.isRequired,
onLabelToFilterAdd: PropTypes.func.isRequired,
onLabelFromFilterRemove: PropTypes.func.isRequired,
onLabelCreate: PropTypes.func.isRequired,
onLabelUpdate: PropTypes.func.isRequired,
onLabelMove: PropTypes.func.isRequired,
onLabelDelete: PropTypes.func.isRequired,
onTextFilterUpdate: PropTypes.func.isRequired,
};
export default BoardActions;

View file

@ -1,26 +0,0 @@
:global(#app) {
.action {
align-items: center;
display: flex;
flex: 0 0 auto;
}
.actions {
align-items: center;
display: flex;
gap: 20px;
justify-content: flex-start;
margin: 20px 20px;
}
.wrapper {
overflow-x: auto;
overflow-y: hidden;
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
}

View file

@ -1,190 +0,0 @@
import React, { useCallback, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { Icon } from 'semantic-ui-react';
import { usePopup } from '../../lib/popup';
import { Input } from '../../lib/custom-ui';
import User from '../User';
import Label from '../Label';
import BoardMembershipsStep from '../BoardMembershipsStep';
import LabelsStep from '../LabelsStep';
import styles from './Filters.module.scss';
const Filters = React.memo(
({
users,
labels,
filterText,
allBoardMemberships,
allLabels,
canEdit,
onUserAdd,
onUserRemove,
onLabelAdd,
onLabelRemove,
onLabelCreate,
onLabelUpdate,
onLabelMove,
onLabelDelete,
onTextFilterUpdate,
}) => {
const [t] = useTranslation();
const [isSearchFocused, setIsSearchFocused] = useState(false);
const searchFieldRef = useRef(null);
const cancelSearch = useCallback(() => {
onTextFilterUpdate('');
searchFieldRef.current.blur();
}, [onTextFilterUpdate]);
const handleRemoveUserClick = useCallback(
(id) => {
onUserRemove(id);
},
[onUserRemove],
);
const handleRemoveLabelClick = useCallback(
(id) => {
onLabelRemove(id);
},
[onLabelRemove],
);
const handleSearchChange = useCallback(
(_, { value }) => {
onTextFilterUpdate(value);
},
[onTextFilterUpdate],
);
const handleSearchFocus = useCallback(() => {
setIsSearchFocused(true);
}, []);
const handleSearchKeyDown = useCallback(
(event) => {
if (event.key === 'Escape') {
cancelSearch();
}
},
[cancelSearch],
);
const handleSearchBlur = useCallback(() => {
setIsSearchFocused(false);
}, []);
const handleCancelSearchClick = useCallback(() => {
cancelSearch();
}, [cancelSearch]);
const BoardMembershipsPopup = usePopup(BoardMembershipsStep);
const LabelsPopup = usePopup(LabelsStep);
const isSearchActive = filterText || isSearchFocused;
return (
<>
<span className={styles.filter}>
<BoardMembershipsPopup
items={allBoardMemberships}
currentUserIds={users.map((user) => user.id)}
title="common.filterByMembers"
onUserSelect={onUserAdd}
onUserDeselect={onUserRemove}
>
<button type="button" className={styles.filterButton}>
<span className={styles.filterTitle}>{`${t('common.members')}:`}</span>
{users.length === 0 && <span className={styles.filterLabel}>{t('common.all')}</span>}
</button>
</BoardMembershipsPopup>
{users.map((user) => (
<span key={user.id} className={styles.filterItem}>
<User
name={user.name}
avatarUrl={user.avatarUrl}
size="tiny"
onClick={() => handleRemoveUserClick(user.id)}
/>
</span>
))}
</span>
<span className={styles.filter}>
<LabelsPopup
items={allLabels}
currentIds={labels.map((label) => label.id)}
title="common.filterByLabels"
canEdit={canEdit}
onSelect={onLabelAdd}
onDeselect={onLabelRemove}
onCreate={onLabelCreate}
onUpdate={onLabelUpdate}
onMove={onLabelMove}
onDelete={onLabelDelete}
>
<button type="button" className={styles.filterButton}>
<span className={styles.filterTitle}>{`${t('common.labels')}:`}</span>
{labels.length === 0 && <span className={styles.filterLabel}>{t('common.all')}</span>}
</button>
</LabelsPopup>
{labels.map((label) => (
<span key={label.id} className={styles.filterItem}>
<Label
name={label.name}
color={label.color}
size="small"
onClick={() => handleRemoveLabelClick(label.id)}
/>
</span>
))}
</span>
<span className={styles.filter}>
<Input
ref={searchFieldRef}
value={filterText}
placeholder={t('common.searchCards')}
icon={
isSearchActive ? (
<Icon link name="cancel" onClick={handleCancelSearchClick} />
) : (
'search'
)
}
className={classNames(styles.search, !isSearchActive && styles.searchInactive)}
onFocus={handleSearchFocus}
onKeyDown={handleSearchKeyDown}
onChange={handleSearchChange}
onBlur={handleSearchBlur}
/>
</span>
</>
);
},
);
Filters.propTypes = {
/* eslint-disable react/forbid-prop-types */
users: PropTypes.array.isRequired,
labels: PropTypes.array.isRequired,
filterText: PropTypes.string.isRequired,
allBoardMemberships: PropTypes.array.isRequired,
allLabels: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */
canEdit: PropTypes.bool.isRequired,
onUserAdd: PropTypes.func.isRequired,
onUserRemove: PropTypes.func.isRequired,
onLabelAdd: PropTypes.func.isRequired,
onLabelRemove: PropTypes.func.isRequired,
onLabelCreate: PropTypes.func.isRequired,
onLabelUpdate: PropTypes.func.isRequired,
onLabelMove: PropTypes.func.isRequired,
onLabelDelete: PropTypes.func.isRequired,
onTextFilterUpdate: PropTypes.func.isRequired,
};
export default Filters;

View file

@ -1,3 +0,0 @@
import BoardActions from './BoardActions';
export default BoardActions;

View file

@ -1,107 +0,0 @@
import { dequal } from 'dequal';
import omit from 'lodash/omit';
import React, { useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Form, Menu, Radio, Segment } from 'semantic-ui-react';
import { Popup } from '../../lib/custom-ui';
import { BoardMembershipRoles } from '../../constants/Enums';
import styles from './BoardMembershipPermissionsSelectStep.module.scss';
const BoardMembershipPermissionsSelectStep = React.memo(
({ defaultData, title, buttonContent, onSelect, onBack, onClose }) => {
const [t] = useTranslation();
const [data, setData] = useState(() => ({
role: BoardMembershipRoles.EDITOR,
canComment: null,
...defaultData,
}));
const handleSelectRoleClick = useCallback((role) => {
setData((prevData) => ({
...prevData,
role,
canComment: role === BoardMembershipRoles.VIEWER ? !!prevData.canComment : null,
}));
}, []);
const handleSettingChange = useCallback((_, { name: fieldName, checked: value }) => {
setData((prevData) => ({
...prevData,
[fieldName]: value,
}));
}, []);
const handleSubmit = useCallback(() => {
if (!dequal(data, defaultData)) {
onSelect(data.role === BoardMembershipRoles.VIEWER ? data : omit(data, 'canComment'));
}
onClose();
}, [defaultData, onSelect, onClose, data]);
return (
<>
<Popup.Header onBack={onBack}>
{t(title, {
context: 'title',
})}
</Popup.Header>
<Popup.Content>
<Form onSubmit={handleSubmit}>
<Menu secondary vertical className={styles.menu}>
<Menu.Item
active={data.role === BoardMembershipRoles.EDITOR}
onClick={() => handleSelectRoleClick(BoardMembershipRoles.EDITOR)}
>
<div className={styles.menuItemTitle}>{t('common.editor')}</div>
<div className={styles.menuItemDescription}>
{t('common.canEditContentOfBoard')}
</div>
</Menu.Item>
<Menu.Item
active={data.role === BoardMembershipRoles.VIEWER}
onClick={() => handleSelectRoleClick(BoardMembershipRoles.VIEWER)}
>
<div className={styles.menuItemTitle}>{t('common.viewer')}</div>
<div className={styles.menuItemDescription}>{t('common.canOnlyViewBoard')}</div>
</Menu.Item>
</Menu>
{data.role === BoardMembershipRoles.VIEWER && (
<Segment basic className={styles.settings}>
<Radio
toggle
name="canComment"
checked={data.canComment}
label={t('common.canComment')}
onChange={handleSettingChange}
/>
</Segment>
)}
<Button positive content={t(buttonContent)} />
</Form>
</Popup.Content>
</>
);
},
);
BoardMembershipPermissionsSelectStep.propTypes = {
defaultData: PropTypes.object, // eslint-disable-line react/forbid-prop-types
title: PropTypes.string,
buttonContent: PropTypes.string,
onSelect: PropTypes.func.isRequired,
onBack: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
BoardMembershipPermissionsSelectStep.defaultProps = {
defaultData: undefined,
title: 'common.selectPermissions',
buttonContent: 'action.selectPermissions',
};
export default BoardMembershipPermissionsSelectStep;

View file

@ -1,18 +0,0 @@
:global(#app) {
.menu {
margin: 0 auto 8px;
width: 100%;
}
.menuItemDescription {
opacity: 0.5;
}
.menuItemTitle {
margin-bottom: 8px;
}
.settings {
margin: 0 0 8px;
}
}

View file

@ -1,3 +0,0 @@
import BoardMembershipPermissionsSelectStep from './BoardMembershipPermissionsSelectStep';
export default BoardMembershipPermissionsSelectStep;

View file

@ -1,103 +0,0 @@
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Menu } from 'semantic-ui-react';
import { Input, Popup } from '../../lib/custom-ui';
import { useField } from '../../hooks';
import Item from './Item';
import styles from './BoardMembershipsStep.module.scss';
const BoardMembershipsStep = React.memo(
({ items, currentUserIds, title, onUserSelect, onUserDeselect, onBack }) => {
const [t] = useTranslation();
const [search, handleSearchChange] = useField('');
const cleanSearch = useMemo(() => search.trim().toLowerCase(), [search]);
const filteredItems = useMemo(
() =>
items.filter(
({ user }) =>
user.email.includes(cleanSearch) ||
user.name.toLowerCase().includes(cleanSearch) ||
(user.username && user.username.includes(cleanSearch)),
),
[items, cleanSearch],
);
const searchField = useRef(null);
const handleUserSelect = useCallback(
(id) => {
onUserSelect(id);
},
[onUserSelect],
);
const handleUserDeselect = useCallback(
(id) => {
onUserDeselect(id);
},
[onUserDeselect],
);
useEffect(() => {
searchField.current.focus({
preventScroll: true,
});
}, []);
return (
<>
<Popup.Header onBack={onBack}>
{t(title, {
context: 'title',
})}
</Popup.Header>
<Popup.Content>
<Input
fluid
ref={searchField}
value={search}
placeholder={t('common.searchMembers')}
icon="search"
onChange={handleSearchChange}
/>
{filteredItems.length > 0 && (
<Menu secondary vertical className={styles.menu}>
{filteredItems.map((item) => (
<Item
key={item.id}
isPersisted={item.isPersisted}
isActive={currentUserIds.includes(item.user.id)}
user={item.user}
onUserSelect={() => handleUserSelect(item.user.id)}
onUserDeselect={() => handleUserDeselect(item.user.id)}
/>
))}
</Menu>
)}
</Popup.Content>
</>
);
},
);
BoardMembershipsStep.propTypes = {
/* eslint-disable react/forbid-prop-types */
items: PropTypes.array.isRequired,
currentUserIds: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */
title: PropTypes.string,
onUserSelect: PropTypes.func.isRequired,
onUserDeselect: PropTypes.func.isRequired,
onBack: PropTypes.func,
};
BoardMembershipsStep.defaultProps = {
title: 'common.members',
onBack: undefined,
};
export default BoardMembershipsStep;

View file

@ -1,21 +0,0 @@
:global(#app) {
.menu {
margin: 8px auto 0;
max-height: 60vh;
overflow-x: hidden;
overflow-y: auto;
width: 100%;
&::-webkit-scrollbar {
width: 5px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
border-radius: 3px;
}
}
}

View file

@ -1,44 +0,0 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Menu } from 'semantic-ui-react';
import User from '../User';
import styles from './Item.module.scss';
const Item = React.memo(({ isPersisted, isActive, user, onUserSelect, onUserDeselect }) => {
const handleToggleClick = useCallback(() => {
if (isActive) {
onUserDeselect();
} else {
onUserSelect();
}
}, [isActive, onUserSelect, onUserDeselect]);
return (
<Menu.Item
active={isActive}
disabled={!isPersisted}
className={classNames(styles.menuItem, isActive && styles.menuItemActive)}
onClick={handleToggleClick}
>
<span className={styles.user}>
<User name={user.name} avatarUrl={user.avatarUrl} />
</span>
<div className={classNames(styles.menuItemText, isActive && styles.menuItemTextActive)}>
{user.name}
</div>
</Menu.Item>
);
});
Item.propTypes = {
isPersisted: PropTypes.bool.isRequired,
isActive: PropTypes.bool.isRequired,
user: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
onUserSelect: PropTypes.func.isRequired,
onUserDeselect: PropTypes.func.isRequired,
};
export default Item;

View file

@ -1,3 +0,0 @@
import BoardMembershipsStep from './BoardMembershipsStep';
export default BoardMembershipsStep;

View file

@ -1,3 +0,0 @@
import AddStep from './AddStep';
export default AddStep;

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