daniil_shornikov-kanban1
Version:
A basic JavaScript math library for number addition
1,288 lines (1,181 loc) • 86 kB
JavaScript
class Kanban {
constructor(initialState = null, containerId = 'kanban-board',language) {
const columnTranslations = {
en: ["Starting Tasks", "Main Tasks", "Side Tasks"],
ru: ["Стартовые задачи", "Основные задачи", "Побочные задачи"],
};
this.translations = {
en: {
addTask: "Add New Task",
taskText: "Task Text:",
taskPlaceholder: "Enter task text",
taskStatus: "Status:",
taskDate: "Date:",
confirmTaskButton: "Add Task",
addAuthor: "Add New Author",
authorName: "Author Name:",
authorPlaceholder: "Enter author name",
authorColor: "Author Color:",
confirmAuthorButton: "Add Author",
deleteAuthor: "Delete Author",
selectAuthor: "Select Author:",
confirmDeleteButton: "Delete Author",
addColumn: "Add New Column",
columnName: "Column Name:",
columnPlaceholder: "Enter column name",
confirmColumnButton: "Add Column",
selectMode: "Select Add Mode:",
closeButton: "Close",
complete: "Complete",
cancel: "Cancel",
edit: "Edit",
delete: "Delete",
add:"Add",
save:"Save",
},
ru: {
addTask: "Добавить новую задачу",
taskText: "Текст задачи:",
taskPlaceholder: "Введите текст задачи",
taskStatus: "Статус:",
taskDate: "Дата:",
confirmTaskButton: "Добавить задачу",
addAuthor: "Добавить нового автора",
authorName: "Имя исполнителя:",
authorPlaceholder: "Введите имя исполнителя",
authorColor: "Цвет исполнителя:",
confirmAuthorButton: "Добавить автора",
deleteAuthor: "Удалить автора",
selectAuthor: "Выберите исполнителя:",
confirmDeleteButton: "Удалить автора",
addColumn: "Добавить новую колонку",
columnName: "Имя колонки:",
columnPlaceholder: "Введите имя колонки",
confirmColumnButton: "Добавить колонку",
selectMode: "Выберите режим добавления:",
closeButton: "Закрыть",
add:"Добавить",
complete: "Выполнить",
cancel: "Отменить",
edit: "Редактировать",
delete: "Удалить",
save:"Сохранить"
}
};
this.language = language||initialState?.language||"en";
document.documentElement.setAttribute('lang', this.language);
this.columns = initialState?.columns || columnTranslations[this.language];
this.tasks = initialState?.tasks || [];
this.authors = initialState?.authors || [];
this.containerId = containerId;
this.taskIdCounter = this.tasks.length > 0 ? Math.max(...this.tasks.map(task => task.id)) + 1 : 1;
this.draggedTask = null;
this.draggedColumn = null;
this.dragOffsetX = 0;
this.dragOffsetY = 0;
this.editingTaskId = null;
this.selectedMode = 'task';
this.trash_can = initialState?.trash || true;
this.recycler_name = "recycler";
this.settingsPanelVisible = false;
this.minWidthColumn = 250;
this.startminWidthColumn = 250;
this.padding = 20;
this.paddingColumn = 20;
this.minWidthRecycler = 100;
this.switchColumn = false;
this.sortdate = false;
this.max_height_column=350;
this.reversedate = false;// Добавлена переменная sortdate
this.switchTasks = true
this.buttonChanges = false;
this.buttonSwitchColumn = false;
this.moveCompletedTasksToEnd = true; // Добавлено свойство
this.functions = initialState?.functions||{
add_author:this.handleAddAssigneeToTask.bind(this),
edit: this.handleEdit.bind(this),
delete_author:this.handleDeleteAssigneeToTask.bind(this),
}
this.icons=initialState?.icons||{
url_add: "",
hover_add:"blue",
background_add:"white",
size_add:"50%",
url_delete:"",
hover_delete:"#2980b9",
background_delete:"white",
size_delete:"50%",
url_add_author:"",
url_del_author:"",
hover_add_author:"#2980b9",
hover_del_author:"#2980b9",
background_add_author:"white",
background_del_author:"white",
size_add_author:"50%",
size_del_author:"50%",
url_close:"",
hover_close:"#2980b9",
background_close:"white",
size_close:"100%",
menu_color:"white",
menu_size:"24px",
};
this.taskStyles = initialState?.taskStyles || { // стили задач по умолчанию
textColor: '#333333',
fontFamily: 'Arial, sans-serif',
backgroundColor: '#ffffff',
fontSize: '1em',
completedTaskColor: '#d4edda' // Зеленый цвет для выполненных задач по умолчанию
};
this.columnStyles = initialState?.columnStyles || { // стили колонок по умолчанию
headerColor: '#3498db',
backgroundColor: '#ffffff',
edit_width:"200px",
};
this.mainCSSVariables = initialState?.mainCSSVariables || {
primaryColor: '#3498db',
secondaryColor: '#2ecc71',
backgroundColor: '#ecf0f1',
cardBackground: '#ffffff',
textColor: '#333333',
borderColor: '#cccccc',
shadowColor: 'rgba(0, 0, 0, 0.1)',
fontFamily: 'Arial, sans-serif'
};
this.applyStyles();
this.render();
this.render()
}
appendHTMLToStyle() {
const styleElement = document.querySelector('style');
if (styleElement) {
let newStyle = styleElement.innerHTML.replace(/--kanban-column-min-width:.*?;/, `--kanban-column-min-width: ${this.minWidthColumn}px;`);
if (this.columns.includes(this.recycler_name)) {
newStyle = newStyle.replace(/--kanban-recycler-width:.*?;/, `--kanban-recycler-width: 250px;`);
} else {
newStyle = newStyle.replace(/--kanban-recycler-width:.*?;/, `--kanban-recycler-width: 0px;`);
}
styleElement.innerHTML = newStyle;
} else {
console.error("Тег <style> с селектором не найден.");
}
}
seticonsVariables(styles) {
this.icons = { ...this.icons, ...styles };
this._updateCSSVariables();
}
setfunctions(func) {
this.functions = { ...this.functions, ...func };
this.render();
}
setMainCSSVariables(variables) {
this.mainCSSVariables = { ...this.mainCSSVariables, ...variables };
this._updateCSSVariables();
}
setTaskStyles(styles) {
this.taskStyles = { ...this.taskStyles, ...styles };
this._updateCSSVariables();
}
setColumnStyles(styles) {
this.columnStyles = { ...this.columnStyles, ...styles };
this._updateCSSVariables();
}
_updateCSSVariables() {
const styleElement = document.querySelector('style');
if (!styleElement) {
console.error("Тег <style> с селектором не найден.");
return;
}
try {
let newStyle = styleElement.innerHTML;
// Обновляем основные CSS-переменные
for (const [key, value] of Object.entries(this.mainCSSVariables)) {
newStyle = newStyle.replace(new RegExp(`(--${this._camelToKebab(key)}):\\s*[^;]+;`, 'g'), `\$1: ${value};`);
}
// Обновляем стили задач
newStyle = newStyle.replace(/--task-text-color:.*?;/, `--task-text-color: ${this.taskStyles.textColor};`);
newStyle = newStyle.replace(/--task-font-family:.*?;/, `--task-font-family: ${this.taskStyles.fontFamily};`);
newStyle = newStyle.replace(/--task-background-color:.*?;/, `--task-background-color: ${this.taskStyles.backgroundColor};`);
newStyle = newStyle.replace(/--task-font-size:.*?;/, `--task-font-size: ${this.taskStyles.fontSize};`);
newStyle = newStyle.replace(/--completed-task-color:.*?;/, `--completed-task-color: ${this.taskStyles.completedTaskColor};`);
// Обновляем стили колонок
newStyle = newStyle.replace(/--edit-width:.*?;/, `--edit-width: ${this.columnStyles.edit_width};`);
const match = newStyle.match(/--edit-width:.*?;/)
newStyle = newStyle.replace(/--column-header-color:.*?;/, `--column-header-color: ${this.columnStyles.headerColor};`);
newStyle = newStyle.replace(/--column-background-color:.*?;/, `--column-background-color: ${this.columnStyles.backgroundColor};`);
newStyle = newStyle.replace(/--url-add:.*?;/g, `--url-add: url(${this.icons.url_add});`);
newStyle = newStyle.replace(/--url-delete:.*?;/g, `--url-delete: url(${this.icons.url_delete});`);
newStyle = newStyle.replace(/--url-add-author:.*?;/g, `--url-add-author: url(${this.icons.url_add_author});`);
newStyle = newStyle.replace(/--url-del-author:.*?;/g, `--url-del-author: url(${this.icons.url_del_author});`);
newStyle = newStyle.replace(/--url-close:.*?;/g, `--url-close: url(${this.icons.url_close});`);
newStyle = newStyle.replace(/--hover-add:.*?;/g, `--hover-add: ${this.icons.hover_add};`);
newStyle = newStyle.replace(/--hover-delete:.*?;/g, `--hover-delete: ${this.icons.hover_delete};`);
newStyle = newStyle.replace(/--hover-add-author:.*?;/g, `--hover-add-author: ${this.icons.hover_add_author};`);
newStyle = newStyle.replace(/--hover-del-author:.*?;/g, `--hover-del-author: ${this.icons.hover_del_author};`);
newStyle = newStyle.replace(/--hover-close:.*?;/g, `--hover-close: ${this.icons.hover_close};`);
newStyle = newStyle.replace(/--background-add:.*?;/g, `--background-add: ${this.icons.background_add};`);
newStyle = newStyle.replace(/--background-delete:.*?;/g, `--background-delete: ${this.icons.background_delete};`);
newStyle = newStyle.replace(/--background-add-author:.*?;/g, `--background-add-author: ${this.icons.background_add_author};`);
newStyle = newStyle.replace(/--background-del-author:.*?;/g, `--background-del-author: ${this.icons.background_del_author};`);
newStyle = newStyle.replace(/--background-close:.*?;/g, `--background-close: ${this.icons.background_close};`);
newStyle = newStyle.replace(/--size-add:.*?;/g, `--size-add: ${this.icons.size_add};`);
newStyle = newStyle.replace(/--size-delete:.*?;/g, `--size-delete: ${this.icons.size_delete};`);
newStyle = newStyle.replace(/--size-add-author:.*?;/g, `--size-add-author: ${this.icons.size_add_author};`);
newStyle = newStyle.replace(/--size-del-author:.*?;/g, `--size-del-author: ${this.icons.size_del_author};`);
newStyle = newStyle.replace(/--size-close:.*?;/g, `--size-close: ${this.icons.size_close};`);
// Обновляем стиль
styleElement.innerHTML = newStyle;
this.render()
} catch (error) {
console.error("Произошла ошибка при обновлении CSS-переменных:", error);
}
}
_camelToKebab(camelCaseString) {
return camelCaseString.replace(/([A-Z])/g, '-$1').toLowerCase();
}
handleModeChange(mode) {
this.selectedMode = mode;
this.render();
}
calculateColumnsPerRow(availableWidth, totalColumns) {
const maxstr = Math.ceil(availableWidth / (this.startminWidthColumn + this.padding));
const result = Math.ceil(totalColumns / maxstr);
return Math.ceil(totalColumns / result);
}
applyStyles() {
const style = document.createElement('style');
style.innerHTML = `
/* Общие стили */
:root {
--primary-color: ${this.mainCSSVariables.primaryColor};
--secondary-color: ${this.mainCSSVariables.secondaryColor};
--background-color: ${this.mainCSSVariables.backgroundColor};
--card-background: ${this.mainCSSVariables.cardBackground};
--text-color: ${this.mainCSSVariables.textColor};
--border-color: ${this.mainCSSVariables.borderColor};
--shadow-color: ${this.mainCSSVariables.shadowColor};
--font-family: ${this.mainCSSVariables.fontFamily};
--kanban-column-min-width: ${this.minWidthColumn}px;
--kanban-recycler-color: #777777; /* Серый цвет для recycler */
--kanban-recycler-min-width: ${this.minWidthRecycler}px; /* Изначальная ширина 0 */
--settings-panel-display: none; /* Изначально скрываем панель */
--close-button-color: red;
--task-menu-background: var(--card-background);
--task-menu-border: 1px solid var(--border-color);
--task-menu-shadow: 0 2px 5px var(--shadow-color);
/* Стили для задач */
--task-text-color: ${this.taskStyles.textColor};
--task-font-family: ${this.taskStyles.fontFamily};
--task-background-color: ${this.taskStyles.backgroundColor};
--task-font-size: ${this.taskStyles.fontSize};
--completed-task-color: ${this.taskStyles.completedTaskColor};
/* Стили для колонок */
--edit-width: ${this.columnStyles.edit_width};
--column-header-color: ${this.columnStyles.headerColor};
--column-background-color: ${this.columnStyles.backgroundColor};
/* Иконки добавления */
--url-add:url(${this.icons.url_delete});
--url-delete:url(${this.icons.url_delete});
--url-add-author:url(${this.icons.url_add_author});
--url-del-author:url(${this.icons.url_del_author});
--url-close:url(${this.icons.url_close});
--hover-add:${this.icons.hover_add};
--hover-delete:${this.icons.hover_delete};
--hover-add-author:${this.icons.hover_add_author};
--hover-del-author:${this.icons.hover_del_author};
--hover-close:${this.icons.hover_close};
--background-add:${this.icons.background_add};
--background-delete:${this.icons.background_delete};
--background-add-author:${this.icons.background_add_author};
--background-del-author:${this.icons.background_del_author};
--background-close:${this.icons.background_close};
--size-add:${this.icons.size_add};
--size-delete:${this.icons.size_delete};
--size-add-author:${this.icons.size_add_author};
--size-del-author:${this.icons.size_del_author};
--size-close:${this.icons.size_close};
--menu-color:${this.icons.menu_color};
--menu-size:${this.icons.menu_size};
/* Иконки удаления */
}
.board-container {
margin: 15px auto; /* Уменьшили отступ */
padding: 5px; /* Уменьшили паддинг */
box-sizing: border-box;
position: relative;
}
body{
overflow-wrap: break-word;
}
#${this.containerId} {
color: var(--text-color);
font-family: var(--font-family);
background-color: var(--background-color);
width: 100%;
display: flex;
flex-direction:row;
align-items: flex-start;
box-sizing: border-box;
border-radius: 8px; /* Уменьшили радиус */
background-color: var(--background-color);
box-shadow: 0 4px 10px var(--shadow-color); /* Уменьшили тень */
}
.kanban-columns-wrapper {
display: flex;
width: 100%;
}
.kanban-columns-container {
flex: 1;
display: flex;
flex-wrap: wrap;
align-items: flex-start;
justify-content: flex-start;
box-sizing: border-box;
align-items: stretch;
}
.kanban-column {
max-height: ${this.max_height_column}px;;
flex: 1 0 var(--kanban-column-min-width);
min-width: var(--kanban-column-min-width);
padding: ${this.paddingColumn}px;
margin: 10px;
background-color: var(--column-background-color);
border-radius: 10px;
box-shadow: 0 3px 10px var(--shadow-color);
display: flex;
flex-direction: column;
overflow-y: scroll;
box-sizing: border-box;
transition: background-color 0.3s ease;
}
.kanban-column:hover {
background-color: #f2f2f2;
}
.kanban-column.recycler {
background-color: var(--kanban-recycler-color);
}
.kanban-column-header {
font-weight: bold;
text-align: center;
margin-bottom: 10px; /* Уменьшили отступ */
color: var(--column-header-color);
font-size: 1em; /* Уменьшили размер шрифта */
border-bottom: 1px solid var(--border-color); /* Уменьшили толщину границы */
padding-bottom: 5px; /* Уменьшили паддинг */
}
.kanban-task {
background-color: var(--task-background-color);
padding: 10px;
margin-bottom: 5px;
border: 1px solid var(--border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
transition: transform 0.2s ease, box-shadow 0.2s ease;
box-sizing: border-box;
box-shadow: 0 1px 3px var(--shadow-color);
font-family: var(--task-font-family);
font-size: var(--task-font-size);
color: var(--task-text-color);
}
.kanban-task.completed {
background-color: var(--completed-task-color);
opacity: 0.7;
}
.kanban-task:hover {
transform: translateY(-3px);
box-shadow: 0 4px 10px var(--shadow-color);
}
.widdth {
width: 100%;
max-width: 400px;
box-sizing: border-box;
margin-bottom: 20px;
}
.edit-button, .add-button,del-button {
border: none;
cursor: pointer;
color: var(--primary-color);
transition: color 0.3s ease;
padding: 5px;
border-radius: 5px;
}
.edit-button:hover, .add-button:hover,del-button:hover {
color: var(--secondary-color);
background-color: rgba(0, 0, 0, 0.05);
}
.add-button {
top:5px;
width:20px;
height:20px;
background-image: var(--url-add-author);
background-color:var(--background-add-author);
background-size: var(--size-add-author);
background-repeat: no-repeat;
background-position: center;
color: white;
border: none;
border-radius: 5px;
padding: 8px 12px;
transition: background-color 0.3s ease;
}
.del-button {
top:10px;
margin:5px;
width:20px;
height:20px;
background-image: var(--url-del-author);
background-color:var(--background-del-author);
background-size: var(--size-del-author);
background-repeat: no-repeat;
background-position: center;
color: white;
border: none;
border-radius: 5px;
padding: 8px 12px;
transition: background-color 0.3s ease;
}
.add-button:hover {
background-color:var(--hover-add-author);
}
.del-button:hover {
background-color:var(--hover-del-author);
}
.settings-modal {
width:100%;
background: white;
/* другие стили для вашего блока */
}
.close-button {
width:30px;
height:30px;
border-radius: 50%;
color:var(--close-button-color);
background-image: var(--url-close);
background-color:var(--background-close);
background-size: var(--size-close);
background-repeat: no-repeat;
background-position: center;
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
font-size: 14px;
}
.edit {
margin-top: 20px;
padding: 15px;
border: 1px solid var(--border-color);
background-color: #ffffff;
border-radius: 8px;
width: var(--edit-width);
height:auto;
box-sizing: border-box;
box-shadow: 0 2px 5px var(--shadow-color);
}
.edit input[type="text"],
.edit input[type="date"],
.edit select {
width: 100%;
padding: 8px;
margin-bottom: 10px;
border: 1px solid var(--border-color);
border-radius: 5px;
box-sizing: border-box;
font-family: var(--font-family);
}
.styled-form label{
font-weight: bold;
display: block;
margin-bottom: 5px;
}
.styled-form input[type="text"],
.styled-form input[type="date"],
.styled-form select {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 5px;
box-sizing: border-box;
font-family: var(--font-family);
transition: border-color 0.3s ease;
}
.styled-form input[type="text"]:focus,
.styled-form input[type="date"]:focus,
.styled-form select:focus{
border-color:var(--primary-color)
}
.styled-form input[type="text"]::placeholder,
.styled-form input[type="date"]::placeholder,
.styled-form select::placeholder {
color: #aaa;
}
.styled-form{
background-color: var(--card-background);
padding: 20px;
border: 1px solid var(--border-color);
border-radius: 10px;
box-shadow: 0 4px 8px var(--shadow-color);
transition: box-shadow 0.3s ease;
margin-bottom: 20px;
box-sizing: border-box;
}
.button-back{
background-color:black;
width:43px;
height:43px;
border:2px solid;
}
.styled-button {
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 5px;
padding: 10px 20px;
cursor: pointer;
transition: background-color 0.3s ease;
font-family: var(--font-family);
font-size: 1em;
box-shadow: 0 2px 5px var(--shadow-color);
margin-top: 10px
}
.styled-form button:hover{
background-color: #2980b9;
}
.styled-button:hover {
background-color: #2980b9;
}
#root {
--column-count: 4;
}
[data-column-count="1"] {
--column-count: 1;
}
[data-column-count="2"] {
--column-count: 2;
}
[data-column-count="3"] {
--column-count: 3;
}
[data-column-count="4"] {
--column-count: 4;
}
[data-column-count="5"] {
--column-count: 5;
}
[data-column-count="6"] {
--column-count: 6;
}
.edit button {
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 5px;
padding: 8px 12px;
cursor: pointer;
transition: background-color 0.3s ease;
font-family: var(--font-family);
}
.edit button:hover {
background-color: #2980b9;
}
.task-date {
font-size: 0.8em;
color: #777;
text-align: right;
margin-top: 5px;
}
.authors-container {
display: flex;
align-items: center;
flex-wrap: wrap;
margin-top: 10px;
}
.assignee-circle {
width: 25px;
height: 25px;
border-radius: 50%;
margin-right: 5px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8em;
color: white;
box-shadow: 0 1px 3px var(--shadow-color);
}
/* Стили для кнопок выбора режима */
.controls {
display: flex;
justify-content: flex-start; /* Выравнивание по левому краю */
gap: 10px;
width: 100%;
box-sizing: border-box;
margin-bottom: 0px;
background-color: var(--card-background); /* Общий фон */
padding: 15px;
border-radius: 10px;
}
.radio-button {
flex: 1;
padding: 8px;
background-color: #dfe6e9;
text-align: center;
cursor: pointer;
border: 1px solid var(--border-color);
transition: background-color 0.3s ease, color 0.3s ease;
box-sizing: border-box;
border-radius: 5px;
color: var(--text-color);
}
.active {
background-color: #4CAF50; /* Зеленый цвет для активного состояния */
color: white;
}
.inactive {
background-color: #f44336; /* Красный цвет для неактивного состояния */
color: white;
}
.radio-button:hover {
background-color: #c7ecee;
}
input[type="radio"] {
display: none;
}
input[type="radio"]:checked + .radio-button {
background-color: var(--primary-color);
color: white;
font-weight: bold;
}
.content-block {
width:100%;
border: 1px solid var(--border-color);
border-radius: 2px;
box-sizing: border-box;
padding: 5px;
background-color: var(--card-background);
box-shadow: 0 2px 2px var(--shadow-color);
position: relative; /* Для позиционирования settings-button */
}
/* Стили для кнопки настроек */
.settings-button {
position: absolute;
top: 10px;
right: 10px; /* Отодвигаем немного вправо */
background-image: var(--url-add);
background-color:var(--hover-add);
background-size: var(--size-add);
background-repeat: no-repeat;
background-position: center;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 50%;
width: 25px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
z-index: 1000;
box-shadow: 2px 2px 5px var(--shadow-color);
}
.delete-button {
top: 10px;
width: 30px;
height: 30px;
right: 10px; /* Отодвигаем немного вправо */
background-image: var(--url-delete);
background-color:var(--background-delete);
background-size: var(--size-delete);
background-repeat: no-repeat;
background-position: center;
color: white;
border: none;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
z-index: 1000;
box-shadow: 2px 2px 5px var(--shadow-color);
}
.change-button {
position: absolute;
top: 10px;
left: 10px; /* Отодвигаем немного вправо */
color: white;
border: none;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
z-index: 1000;
box-shadow: 2px 2px 5px var(--shadow-color);
}
.sort-button {
position: absolute;
top: 10px;
right: 50px; /* Отодвигаем немного вправо */
color: white;
border: none;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
z-index: 1000;
box-shadow: 2px 2px 5px var(--shadow-color);
}
.task-change-button {
position: absolute;
top: 10px;
left: 50px; /* Отодвигаем немного вправо */
color: white;
border: none;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
z-index: 1000;
box-shadow: 2px 2px 5px var(--shadow-color);
}
.settings-button:hover {
background-color: var(--hover-add);
}
.delete-button:hover {
background-color:var(--hover-delete);
}
.close-button:hover {
background-color:var(--hover-close);
}
/* Стили для панели настроек */
.settings-panel {
position: absolute;
top: 0;
right: 0;
background-color: var(--card-background);
box-shadow: -5px 0 15px var(--shadow-color);
z-index:10000;
padding: 20px;
box-sizing: border-box;
border-radius: 10px;
transform: translateX(100%);
transition: transform 0.3s ease-in-out;
display: flex;
flex-direction: column;
align-items: flex-start;
opacity: 0;
visibility: hidden;
}
.settings-panel.open {
transform: translateX(0);
opacity: 1;
visibility: visible;
}
/* Ensure the settings container is always on top */
.settings-container {
position: absolute;
top: 10px;
right: 10px;
z-index: 1002; /* Higher than content-block */
}
.settings-content-wrapper {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
}
.kanban-columns-wrapper {
display: flex;
width: 100%;
}
.kanban-columns-container {
flex: 1;
display: flex;
flex-wrap: wrap;
align-items: flex-start;
justify-content: flex-start;
box-sizing: border-box;
align-items: stretch;
}
.kanban-recycler-container {
width: var(--kanban-recycler-width);
}
/* Styles for the task menu */
.task-menu-button {
position: absolute;
top: 5px;
right: 5px;
background: none;
border: none;
cursor: pointer;
font-size: var(--menu-size);
color: var(--menu-color);
padding: 0;
}
.task-menu {
position: absolute;
background-color: var(--task-menu-background);
border: var(--task-menu-border);
border-radius: 5px;
box-shadow: var(--task-menu-shadow);
display: none;
padding: 5px 0;
z-index:10000;
}
.task-menu.open {
display: block;
}
.task-menu button {
display: block;
width: 100%;
padding: 8px 15px;
border: none;
background: none;
text-align: left;
cursor: pointer;
transition: background-color 0.2s ease;
font-family: var(--font-family);
font-size: 1em;
color: var(--text-color);
}
.task-menu button:hover {
background-color: #f2f2f2;
}
`;
document.head.appendChild(style);
}
toJson() {
return JSON.stringify({
columns: this.columns,
tasks: this.tasks,
authors: this.authors,
trash_can: this.trash_can,
taskStyles: this.taskStyles,
columnStyles: this.columnStyles,
mainCSSVariables: this.mainCSSVariables,
icons: this.icons,
language:this.language
});
}
static fromJson(jsonString, containerId = 'kanban-board') {
try {
const parsed = JSON.parse(jsonString);
return new Kanban(parsed, containerId);
} catch (e) {
console.error("Error creating Kanban from JSON", e);
return null;
}
}
fillColumnSelect() {
const statusSelect = document.getElementById('taskStatus');
if (statusSelect) {
statusSelect.innerHTML = '';
const statuses = this.columns;
statuses.forEach(status => {
const option = document.createElement('option');
option.value = status;
option.textContent = status;
statusSelect.appendChild(option);
});
}
}
async updateContentBlock(contentBlock) {
contentBlock.innerHTML = '';
const settingsForm = document.querySelector('.settings-panel form');
let selectedMode = settingsForm?.querySelector('input[name="addMode"]:checked')?.value || null;
if (!selectedMode) {
this.selectedMode = "task";
} else {
this.selectedMode = selectedMode;
}
const t = this.translations[this.language]; // Получаем переводы для выбранного языка
let form;
if (this.selectedMode === 'task') {
form = document.createElement('form');
form.innerHTML = `
<h3>${t.addTask}</h3>
<label for="taskText">${t.taskText}</label>
<input pattern=".*\\S.*"
title="Пожалуйста, введите текст, не только пробелы." type="text" id="taskText" placeholder="${t.taskPlaceholder}" required>
<label for="taskStatus">${t.taskStatus}</label>
<select id="taskStatus" required></select>
<label for="taskDate">${t.taskDate}</label>
<input type="date" id="taskDate" required lang=${this.language}>
<button type="submit" class="styled-button" id="confirmTaskButton">${t.confirmTaskButton}</button>
`;
contentBlock.appendChild(form);
form.className = "styled-form";
this.fillColumnSelect();
form.addEventListener('submit', async (e) => {
e.preventDefault();
const text = document.getElementById('taskText').value;
const status = document.getElementById('taskStatus').value;
const date = document.getElementById('taskDate').value;
try {
await this.addTask(text, status, date);
form.reset();
} catch (e) {
console.error(e);
alert(e);
}
});
setTimeout(() => {
this.fillColumnSelect();
}, 0);
} else if (this.selectedMode === 'author') {
form = document.createElement('form');
form.innerHTML = `
<h3>${t.addAuthor}</h3>
<label for="authorName">${t.authorName}</label>
<input pattern=".*\\S.*"
title="Пожалуйста, введите текст, не только пробелы." type="text" id="authorName" placeholder="${t.authorPlaceholder}" required>
<label for="authorColor">${t.authorColor}</label>
<input type="color" id="authorColor" required><br>
<button type="submit" class="styled-button" id="confirmAuthorButton">${t.confirmAuthorButton}</button>
`;
contentBlock.appendChild(form);
form.className = "styled-form";
form.addEventListener('submit', async (e) => {
e.preventDefault();
const name = document.getElementById('authorName').value;
const color = document.getElementById('authorColor').value;
try {
await this.addAuthor(name, color);
form.reset();
} catch (e) {
console.error(e);
alert(e);
}
});
} else if (this.selectedMode === 'authorDelete') {
form = document.createElement('form');
form.innerHTML = `
<h3>${t.deleteAuthor}</h3>
<label for="authorSelect">${t.selectAuthor}</label>
<select id="authorSelect" required>
<option value="" disabled selected>${t.selectAuthor}</option>
</select>
<button type="submit" class="styled-button" id="confirmDeleteButton">${t.confirmDeleteButton}</button>
`;
contentBlock.appendChild(form);
form.className = "styled-form";
// Заполнение списка авторов
const selectElement = form.querySelector('#authorSelect');
if (this.authors && this.authors.length > 0) {
this.authors.forEach(author => {
const option = document.createElement('option');
option.value = author.name;
option.innerText = author.name;
selectElement.appendChild(option);
});
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
const selectedAuthorName = selectElement.value;
try {
await this.deleteAuthor(selectedAuthorName);
// Удаляем автора из локального списка
this.authors = this.authors.filter(author => author.name !== selectedAuthorName);
form.reset(); // Сброс формы после удаления
this.render(); // Обновление интерфейса, если необходимо
} catch (e) {
console.error(e);
alert(e);
}
});
} else if (this.selectedMode === 'column') {
form = document.createElement('form');
form.innerHTML = `
<h3>${t.addColumn}</h3>
<label for="columnName">${t.columnName}</label>
<input pattern=".*\\S.*"
title="Пожалуйста, введите текст, не только пробелы." type="text" id="columnName" placeholder="${t.columnPlaceholder}" required>
<button type="submit" class="styled-button" id="confirmColumnButton">${t.confirmColumnButton}</button>
`;
contentBlock.appendChild(form);
form.className = "styled-form";
form.addEventListener('submit', async (e) => {
e.preventDefault();
const name = document.getElementById('columnName').value;
try {
await this.addColumn(name);
form.reset();
} catch (e) {
console.error(e);
alert(e);
}
});
}
if (form) {
form.classList.add('styled-form');
}
}
async addTask(text, status, date, authors = []) {
return new Promise((resolve, reject) => {
if (!this.columns.includes(status)) {
reject(new Error(`status ${status} doesn't exist`));
return;
}
try {
const id = this.taskIdCounter++;
const task = {
id,
text,
status,
authors,
date,
isCompleted: false,
comments: [],
};
this.tasks.push(task);
this.render()
resolve(task);
} catch (error) {
console.error("Error adding task:", error);
reject(error);
}
});
}
editTask(id, updateData) {
return new Promise((resolve, reject) => {
try {
const taskIndex = this.tasks.findIndex(task => task.id === id);
if (taskIndex === -1) {
reject(new Error(`Task with id ${id} not found`));
return;
}
const updatedTask = { ...this.tasks[taskIndex], ...updateData };
this.tasks[taskIndex] = updatedTask;
this.render();
resolve(updatedTask);
} catch (error) {
console.error("Error editing task:", error);
reject(error);
}
});
}
deleteTask(id) {
return new Promise((resolve, reject) => {
try {
const taskIndex = this.tasks.findIndex(task => task.id === id);
if (taskIndex === -1) {
reject(new Error(`Task with id ${id} not found`));
return;
}
if (this.tasks[taskIndex].status == this.recycler_name) {
this.tasks.splice(taskIndex, 1);
const taskIndex2 = this.tasks.findIndex(task => task.status === this.recycler_name);
if (taskIndex2 === -1) {
this.deleteColumn(this.recycler_name)
}
} else {
this.tasks.splice(taskIndex, 1);
}
this.render();
resolve(this.tasks);
} catch (error) {
console.error("Error deleting task:", error);
reject(error);
}
});
}
toggleCompleteTask(id) {
return new Promise((resolve, reject) => {
try {
const taskIndex = this.tasks.findIndex(task => task.id === id);
if (taskIndex === -1) {
reject(new Error(`Task with id ${id} not found`));
return;
}
this.tasks[taskIndex].isCompleted = !this.tasks[taskIndex].isCompleted;
this.render();
resolve(this.tasks[taskIndex]);
} catch (error) {
console.error("Error toggling task completion:", error);
reject(error);
}
});
}
getTasks() {
return this
}
getColumns() {
return this.columns;
}
addAuthor(name, color) {
return new Promise((resolve, reject) => {
try {
const author = {
name,
color
};
this.authors.push(author);
this.render();
resolve(author);
} catch (error) {
console.error("Error adding author:", error);
reject(error);
}
});
}
deleteAuthor(name) {
return new Promise((resolve, reject) => {
try {
const authorIndex = this.authors.findIndex(author => author.name === name);
if (authorIndex === -1) {
throw new Error("Автор не найден");
}
// Удаляем автора из массива
this.authors.splice(authorIndex, 1);
this.render(); // Обновляем интерфейс
resolve(name); // Возвращаем имя удаленного автора
} catch (error) {
console.error("Ошибка при удалении автора:", error);
reject(error);
}
});
}
addColumn(newColumn) {
return new Promise((resolve, reject) => {
try {
if (this.columns.includes(newColumn)) {
reject(new Error(`column ${newColumn} exists`));
return;
}
if (this.columns.includes(this.recycler_name)) {
const index = this.columns.indexOf(this.recycler_name);
this.columns.splice(index, 1)
this.columns.push(newColumn);
this.columns.push(this.recycler_name);
} else {
this.columns.push(newColumn);
}
this.render();
resolve(this.columns);
} catch (e) {
console.error('Error adding new column', e);
reject(e);
}
});
}
removeColumn(columnToRemove) {
return new Promise((resolve, reject) => {
try {
if (!this.columns.includes(columnToRemove)) {
reject(new Error(`column ${columnToRemove} doesn't exists`));
return;
}
this.columns = this.columns.filter(c => c !== columnToRemove);