Skip to content

Commit

Permalink
Контекстное меню
Browse files Browse the repository at this point in the history
  • Loading branch information
delaynore committed Jun 9, 2024
1 parent 3d32f28 commit 937b13d
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 141 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,8 @@
2. Подумать над системой для добавления тегов, например, обычный пользователь может предложить новый тег, а потом админ разрешит его использование. До этого он не будет доступен для выбора в настройках словаря.

7. [x] Локализация


Найденные косяки:
1. Добавление вложения текст кнопки -> добавить и заголовок формы, файл при перезагрузке не восстанавливается, название вложения нельзя пробелы ставить, и сообщение об успешной загрузке вложения изменить
2. стили для дерева посмотреть как в pgAdmin
52 changes: 29 additions & 23 deletions app/Http/Controllers/ConceptController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,14 @@ public function create(Dictionary $dictionary)
return view('concept.create', compact('dictionary'));
}


$parent = Concept::findOrFail(request()->get('parentId'));
return view('concept.create', compact('dictionary', 'parent'));
if (request()->get('brotherId') == null) {
return view('concept.create', compact('dictionary', 'parent'));
}

$brother = Concept::findOrFail(request()->get('brotherId'));
return view('concept.create', compact('dictionary', 'parent', 'brother'));
}

/**
Expand Down Expand Up @@ -72,30 +78,30 @@ public function show(string $dictionary, string $concept)
foreach ($conceptRelationTypes as $conceptRelationType) {

$relatedConcepts1 = DB::table('concept_relations')
->join('concepts as concept_1', 'concept_relations.fk_concept_1_id', '=', 'concept_1.id')
->join('concepts as concept_2', 'concept_relations.fk_concept_2_id', '=', 'concept_2.id')
->join('relation_types', 'concept_relations.fk_relation_type_id', '=', 'relation_types.id')
->select(
'concept_relations.id as relation_id',
'concept_2.id AS concept_id',
'concept_2.name AS concept_name'
)
->where('concept_1.id', '=', $concept->id)
->where('concept_relations.fk_relation_type_id', '=', $conceptRelationType)
->get();
->join('concepts as concept_1', 'concept_relations.fk_concept_1_id', '=', 'concept_1.id')
->join('concepts as concept_2', 'concept_relations.fk_concept_2_id', '=', 'concept_2.id')
->join('relation_types', 'concept_relations.fk_relation_type_id', '=', 'relation_types.id')
->select(
'concept_relations.id as relation_id',
'concept_2.id AS concept_id',
'concept_2.name AS concept_name'
)
->where('concept_1.id', '=', $concept->id)
->where('concept_relations.fk_relation_type_id', '=', $conceptRelationType)
->get();

$relatedConcepts2 = DB::table('concept_relations')
->join('concepts as concept_1', 'concept_relations.fk_concept_1_id', '=', 'concept_1.id')
->join('concepts as concept_2', 'concept_relations.fk_concept_2_id', '=', 'concept_2.id')
->join('relation_types', 'concept_relations.fk_relation_type_id', '=', 'relation_types.id')
->select(
'concept_relations.id as relation_id',
'concept_1.id AS concept_id',
'concept_1.name AS concept_name'
)
->where('concept_2.id', '=', $concept->id)
->where('concept_relations.fk_relation_type_id', '=', $conceptRelationType)
->get();
->join('concepts as concept_1', 'concept_relations.fk_concept_1_id', '=', 'concept_1.id')
->join('concepts as concept_2', 'concept_relations.fk_concept_2_id', '=', 'concept_2.id')
->join('relation_types', 'concept_relations.fk_relation_type_id', '=', 'relation_types.id')
->select(
'concept_relations.id as relation_id',
'concept_1.id AS concept_id',
'concept_1.name AS concept_name'
)
->where('concept_2.id', '=', $concept->id)
->where('concept_relations.fk_relation_type_id', '=', $conceptRelationType)
->get();


$merged = array_merge($relatedConcepts1->toArray(), $relatedConcepts2->toArray());
Expand Down
201 changes: 148 additions & 53 deletions resources/js/tree.js
Original file line number Diff line number Diff line change
@@ -1,74 +1,169 @@
document.addEventListener("DOMContentLoaded", function () {
const TreeView = (() => {
const treeview = document.querySelector('.treeview');

function toggleNode(event) {
const toggleNode = (event) => {
const li = event.target.closest('li');
const ul = li.querySelector('ul');
if (ul) {
ul.classList.toggle('hidden');
const isOpen = !ul.classList.contains('hidden');
const icon = li.querySelector('.tree-view-icon');
const block = li.querySelector('div');
if (isOpen) {
icon.style.transform = 'rotate(-90deg)';
block.classList.add('active');
}
else {
icon.style.transform = '';
block.classList.remove('active');
}

const nodeId = li.getAttribute('data-id');
sessionStorage.setItem(nodeId, isOpen);
const isOpen = toggleElementVisibility(ul);
updateNodeAppearance(li, isOpen);
saveNodeState(li, isOpen);
}
}
};

function hideNode(toggle) {
const hideNode = (toggle, isOpen) => {
const li = toggle.closest('li');
const ul = li.querySelector('ul');
if (ul) {
ul.classList.add('hidden');
const icon = li.querySelector('.tree-view-icon');
const block = li.querySelector('div');
icon.style.transform = '';
block.classList.remove('active');
const nodeId = li.getAttribute('data-id');
sessionStorage.setItem(nodeId, false);
toggleElementVisibility(ul, isOpen);
updateNodeAppearance(li, isOpen);
saveNodeState(li, isOpen);
}
}
};

const togglers = treeview.querySelectorAll('.toggle');
togglers.forEach(toggle => {
toggle.addEventListener('click', toggleNode);
});
const toggleElementVisibility = (element, isOpen = null) => {
if (isOpen !== null) {
element.classList.toggle('hidden', !isOpen);
} else {
element.classList.toggle('hidden');
}
return !element.classList.contains('hidden');
};

const updateNodeAppearance = (li, isOpen) => {
const icon = li.querySelector('.tree-view-icon');
const block = li.querySelector('div');
icon.style.transform = isOpen ? 'rotate(-90deg)' : '';
block.classList.toggle('active', isOpen);
};

const saveNodeState = (li, isOpen) => {
const nodeId = li.getAttribute('data-id');
sessionStorage.setItem(nodeId, isOpen);
};

document.getElementById('collapse-concepts').addEventListener('click', () => {
const initialize = () => {
const togglers = treeview.querySelectorAll('.toggler');
togglers.forEach(toggle => {
hideNode(toggle);
toggle.addEventListener('click', toggleNode);
});
});

treeview.querySelectorAll('li[data-id]').forEach(li => {
const nodeId = li.getAttribute('data-id');
const isOpen = sessionStorage.getItem(nodeId) === 'true';
const ul = li.querySelector('ul');
if (ul && isOpen) {
ul.classList.remove('hidden');
console.log(nodeId);
li.querySelector('.tree-view-icon').style.transform = 'rotate(-90deg)';
li.querySelector('div').classList.add('active');
}
});
let isOpenG = true;
document.getElementById('collapse-concepts').addEventListener('click', () => {
isOpenG = !isOpenG;
togglers.forEach(toggle => {
hideNode(toggle, isOpenG);
});
});

});
treeview.querySelectorAll('li[data-id]').forEach(li => {
const nodeId = li.getAttribute('data-id');
const isOpen = sessionStorage.getItem(nodeId) === 'true';
const ul = li.querySelector('ul');
if (ul && isOpen) {
ul.classList.remove('hidden');
li.querySelector('.tree-view-icon').style.transform = 'rotate(-90deg)';
li.querySelector('div').classList.add('active');
}
});
};

window.addEventListener("load", function () {
return {
initialize
};
})();

const ContextMenu = (() => {
const contextmenu = document.getElementById('contextmenu');
const treeview = document.querySelector('.treeview');
treeview.querySelectorAll('div.open-concept').forEach(div => {
div.addEventListener('dblclick', (e) => {
const route = e.currentTarget.dataset.openConceptRoute;
console.log('click', e.currentTarget.dataset);
window.location.href = route;
let opened = false;
const handleContextMenu = (event, concept) => {
if (opened) {
contextmenu.classList.add('hidden');
opened = false;
}

event.preventDefault();
event.stopPropagation();

const title = contextmenu.querySelector('#conctextmenu-title');
const createParent = contextmenu.querySelector('#conctextmenu-create-parent');
const createBrother = contextmenu.querySelector('#conctextmenu-create-brother');
const editConcept = contextmenu.querySelector('#conctextmenu-edit');
const deleteConcept = contextmenu.querySelector('#conctextmenu-delete');
const openConcept = contextmenu.querySelector('#conctextmenu-open');

title.textContent = concept.name;
createParent.href = concept.parent;
createBrother.href = concept.brother;
editConcept.href = concept.edit;
deleteConcept.action = concept.delete;
openConcept.action = concept.open;

const x = event.pageX;
const y = event.pageY;
const menuWidth = contextmenu.offsetWidth;

contextmenu.style.left = x + menuWidth + 'px';
contextmenu.style.top = y + 'px';
contextmenu.style.right = 'auto';
contextmenu.classList.toggle('hidden');
opened = true;
};

const initialize = () => {
treeview.querySelectorAll('div.open-concept').forEach(div => {
div.addEventListener('click', (e) => {
const route = e.currentTarget.dataset.openConceptRoute;
window.location.href = route;
});
});
})

treeview.querySelectorAll('li[data-el="concept"]').forEach(li => {
const concept = {
name: li.dataset.name,
parent: li.dataset.createParent,
brother: li.dataset.createBrother,
edit: li.dataset.edit,
delete: li.dataset.delete,
open: li.dataset.open
};
const div = li.querySelector('.lidiv');
div.addEventListener('contextmenu', (event) => handleContextMenu(event, concept));
});

document.addEventListener('click', (event) => {
if (!contextmenu.contains(event.target))
contextmenu.classList.add('hidden');
});
document.addEventListener('contextmenu', (event) => {
if (!contextmenu.contains(event.target))
contextmenu.classList.add('hidden');
});
};

const getConceptFromTarget = (target) => {
const li = target.closest('li[data-el="concept"]');
if (li) {
return {
name: li.dataset.name,
parent: li.dataset.createParent,
brother: li.dataset.createBrother,
edit: li.dataset.edit,
delete: li.dataset.delete,
open: li.dataset.open
};
}
return null;
};

return {
initialize
};
})();

// Инициализация модулей
document.addEventListener("DOMContentLoaded", () => {
TreeView.initialize();
ContextMenu.initialize();
});
6 changes: 3 additions & 3 deletions resources/views/components/dashboard/sidebar/menu.blade.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="flex overflow-hidden bg-white border divide-x rounded-lg rtl:flex-row-reverse dark:bg-gray-900 dark:border-gray-700 dark:divide-gray-700">
<a href="{{ route('concept.create', $dictionary) }}" title="{{__('dashboard.sidebar.menu.create')}}" data-tooltip-placement="bottom" data-tooltip-target="tooltip-create-concept" class="px-3 py-1 font-medium text-gray-600 transition-colors duration-200 hover:text-blue-600 dark:hover:text-blue-500 sm:px-6 dark:hover:bg-gray-800 dark:text-gray-300 hover:bg-gray-100">
<a href="{{ route('concept.create', $dictionary) }}" data-tooltip-placement="bottom" data-tooltip-target="tooltip-create-concept" class="px-3 py-1 font-medium text-gray-600 transition-colors duration-200 hover:text-blue-600 dark:hover:text-blue-500 sm:px-6 dark:hover:bg-gray-800 dark:text-gray-300 hover:bg-gray-100">
<svg class="w-5 h-5 sm:w-6 sm:h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m3.75 9v6m3-3H9m1.5-12H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
</svg>
Expand All @@ -8,7 +8,7 @@
{{__('dashboard.sidebar.menu.create')}}
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<a href="{{ route('dictionary.export', $dictionary) }}" data-tooltip-placement="bottom" data-tooltip-target="tooltip-export-concepts" title="{{ __('dashboard.sidebar.menu.export') }}" class="px-3 py-1 font-medium text-gray-600 transition-colors duration-200 hover:text-blue-600 dark:hover:text-blue-500 sm:px-6 dark:hover:bg-gray-800 dark:text-gray-300 hover:bg-gray-100">
<a href="{{ route('dictionary.export', $dictionary) }}" data-tooltip-placement="bottom" data-tooltip-target="tooltip-export-concepts" class="px-3 py-1 font-medium text-gray-600 transition-colors duration-200 hover:text-blue-600 dark:hover:text-blue-500 sm:px-6 dark:hover:bg-gray-800 dark:text-gray-300 hover:bg-gray-100">
<svg class="w-5 h-5 sm:w-6 sm:h-6" data-slot="icon" aria-hidden="true" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 9.75v6.75m0 0-3-3m3 3 3-3m-8.25 6a4.5 4.5 0 0 1-1.41-8.775 5.25 5.25 0 0 1 10.233-2.33 3 3 0 0 1 3.758 3.848A3.752 3.752 0 0 1 18 19.5H6.75Z" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
Expand All @@ -18,7 +18,7 @@
<div class="tooltip-arrow" data-popper-arrow></div>
</div>

<div id="collapse-concepts" data-tooltip-placement="bottom" data-tooltip-target="tooltip-collapse-concepts" title="{{ __('dashboard.sidebar.menu.export') }}" class="px-3 py-1 font-medium text-gray-600 transition-colors duration-200 hover:text-blue-600 dark:hover:text-blue-500 sm:px-6 dark:hover:bg-gray-800 dark:text-gray-300 hover:bg-gray-100">
<div id="collapse-concepts" data-tooltip-placement="bottom" data-tooltip-target="tooltip-collapse-concepts" class="px-3 py-1 font-medium text-gray-600 transition-colors duration-200 cursor-pointer hover:text-blue-600 dark:hover:text-blue-500 sm:px-6 dark:hover:bg-gray-800 dark:text-gray-300 hover:bg-gray-100">
<svg class="w-5 h-5 sm:w-6 sm:h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 13.5H9m4.06-7.19-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
</svg>
Expand Down
2 changes: 1 addition & 1 deletion resources/views/components/tree-view/icon.blade.php
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<svg class="w-4 h-4 transition-transform delay-50 shrink-0 tree-view-icon dark:text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3" stroke="currentColor" class="size-6">
<svg {{ $attributes->merge(['class' => 'w-4 h-4 transition-transform delay-50 shrink-0 tree-view-icon']) }} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
</svg>
Loading

0 comments on commit 937b13d

Please sign in to comment.