15 Commits

Author SHA1 Message Date
11c2222536 UPDATE: change text with contextmenu click 2025-11-22 11:03:04 +01:00
1541d62bb5 FIX: hide color palette 2025-11-22 10:36:44 +01:00
cd13d21e5f Update index.html 2025-11-21 23:27:46 +01:00
d8e3dc8cee de facto done 2025-11-21 10:46:57 +01:00
7f7a4a1ae6 resizing works 2025-11-21 10:43:38 +01:00
e078914ed2 resizing works 2025-11-21 10:42:52 +01:00
9234986b6f FIX: css shape fix 2025-11-20 22:40:04 +01:00
4bbd92920e resize works, but not ideal 2025-11-19 23:26:01 +01:00
2adc151e8e not bad already 2025-11-19 23:11:47 +01:00
1bf1e1b389 Dodano funkcje przybliżania, oddalania i
przewijania planszy.
2025-11-18 22:14:09 +01:00
49f631f854 Update README.md 2025-11-15 12:22:53 +01:00
b1a2dac5e4 Merge remote-tracking branch 'origin/AndriiFunctionality' into AndriiFunctionality 2025-11-14 19:40:01 +01:00
44a52e0627 implementing functions 5,6,7,14 2025-11-14 19:39:48 +01:00
1459d4ab8d Update README.md 2025-11-14 19:38:34 +01:00
ff88da0079 first steps 2025-11-14 15:40:07 +01:00
4 changed files with 40321 additions and 123 deletions

View File

@@ -8,29 +8,29 @@ Funkcjonalności aplikacji koncentrują się na tworzeniu i modyfikacji kształt
## Funkcje:
### Kształty
1. Tworzenie prostokąta
2. Tworzenie rombu
3. Tworzenie elipsy
- [x] 1. Tworzenie prostokąta
- [x] 2. Tworzenie rombu
- [x] 3. Tworzenie elipsy
### Akcje na kształtach
4. Zmiana rozmiaru kształtów.
5. Przeciągnięcie kzstałtu po planszy
6. Usunięcie kształtu
7. Zaznaczenie kształtu
8. Kopiowanie kształtu
9. Wklejanie kształtu
10. Zmiana kolorów kształtów
11. Połączenie kształtów (strzałka ->)
12. Usunięcie kształtów
- [x] 4. Zmiana rozmiaru kształtów.
- [x] 5. Przeciągnięcie kzstałtu po planszy
- [x] 6. Usunięcie kształtu
- [x] 7. Zaznaczenie kształtu
- [x] 8. Kopiowanie kształtu
- [x] 9. Wklejanie kształtu
- [x] 10. Zmiana kolorów kształtów
- [x] 11. Połączenie kształtów (strzałka ->)
- [x] 12. Usunięcie kształtów
### Tekst
13. Dodanie tekstu
14. Usunięcie tekstu
- [x] 13. Dodanie tekstu
- [x] 14. Usunięcie tekstu
### Plansza i akcje na niej
15. Przybliżenie planszy
16. Oddalenie planszy
17. Przewijanie planszy
- [x] 15. Przybliżenie planszy
- [x] 16. Oddalenie planszy
- [x] 17. Przewijanie planszy
*P.S. Jak będzie brakować funkcjonalności:*
- Obracanie kształtu

View File

@@ -10,61 +10,147 @@
</head>
<body>
<div class="toolbar">
<p>Kształty</p>
<div class="shape-wrapper toolbar-wrapper">
<div id="shape-rectangle" class="shape" data-type="rectangle"></div>
<div id="shape-ellipse" class="shape" data-type="ellipse"></div>
<div id="shape-diamond" class="shape" data-type="diamond"></div>
<div id="shape-text" class="shape" data-type="text">Tekst</div>
<div class="toolbar">
<p>Kształty - przeciągnij i upuść</p>
<div class="shape-wrapper toolbar-wrapper">
<div id="shape-rectangle" class="shape" data-type="rectangle"></div>
<div id="shape-ellipse" class="shape" data-type="ellipse"></div>
<div id="shape-diamond" class="shape" data-type="diamond"></div>
<div id="shape-text" class="shape" data-type="text">Tekst</div>
</div>
<p>Akcje</p>
<div class="tools-wrapper toolbar-wrapper">
<button id="btn-delete">Usuń</button>
<button id="btn-copy">Kopiuj</button>
<button id="btn-paste">Wklej</button>
<button id="btn-connect">Połącz</button>
<div class="color-wrapper"><!-- DODANE -->
<button id="btn-change-color">Kolor</button> <!-- DODANE -->
<div id="color-palette" style="position:absolute; display:none; padding:8px; background:#fff; border:1px solid #ccc; border-radius:6px; gap:6px; z-index:2000;"></div>
</div>
</div>
<p>Zoom</p>
<div class="zoom-wrapper toolbar-wrapper">
<button id="zoom-in">+</button>
<button id="zoom-out">-</button>
</div>
<div class="canvas-container">
<div id="paper"></div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@joint/core@4.0.1/dist/joint.js"></script>
<script>
$(document).ready(function () {
//init paper - Start
const graph = new joint.dia.Graph();
const paper = new joint.dia.Paper({
el: $('#paper'),
model: graph,
width: window.innerWidth,
height: window.innerHeight,
gridSize: 20,
<div id="resize-handle" style="position: absolute; display: none; border: 2px dashed #4A90E2; z-index: 10;"></div>
<div class="canvas-container">
<div id="paper"></div>
</div>
<!-- DODANY KONTAINER DLA PALETY KOLORÓW -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>
<script src="joint.js"></script>
<script>
let selectedElement = null;
let activeElementView = null;
let clipboard = null;
$(document).ready(function () {
const graph = new joint.dia.Graph();
const paper = new joint.dia.Paper({
el: $('#paper'),
model: graph,
width: window.innerWidth,
height: window.innerHeight,
gridSize: 20,
preventContextMenu: false,
});
const $resizeHandle = $('#resize-handle');
// --- DODANE FUNKCJE START ---
let connectMode = false;
$('#btn-connect').click(function() {
if (!selectedElement) return;
connectMode = !connectMode;
$(this).css('background', connectMode ? '#e3f2fd' : '#fff');
});
const paletteColors = ['#FF4B4B','#FFA500','#FFD700','#4CAF50','#00BCD4','#2196F3','#9C27B0','#795548','#000000'];
const $palette = $('#color-palette');
$palette.css({ gridTemplateColumns:'repeat(3,28px)' });
paletteColors.forEach(c => {
// ZMIANA TUTAJ: Dodajemy width, height, border i cursor
const tile = $('<div></div>').css({
background: c,
width: '28px', // Dodane
height: '28px', // Dodane
border: '1px solid #444', // Dodane dla widoczności
cursor: 'pointer' // Dodane dla kursora rączki
});
//init paper - END
//create shape - START
function createRectangle(paperX, paperY) {
const rect = new joint.shapes.standard.Rectangle({
position: { x: paperX - 40, y: paperY - 24 },
size: { width: 80, height: 48 },
attrs: {
body: {
fill: 'transparent',
stroke: '#000000',
strokeWidth: 1
},
label: {
text: ''
tile.on('click', function() {
if (selectedElement) {
try {
// Ustawianie koloru
selectedElement.attr('body/fill', c);
if (selectedElement.attributes.type === 'standard.TextBlock') {
selectedElement.attr('label/fill', c);
}
}
} catch(e) { /* ignoruj */ }
}
$palette.hide();
});
$palette.append(tile);
});
$('#btn-change-color').on('click', function() {
$palette.toggleClass('active');
});
$(document).on('click', function(e) {
if (!$(e.target).closest('#color-palette,#btn-change-color').length)
if($palette.hasClass('active'))
$palette.toggleClass('active');
});
paper.on('element:pointerclick', function (elementView) {
if (connectMode && selectedElement && selectedElement !== elementView.model) {
const link = new joint.shapes.standard.Link();
link.source(selectedElement);
link.target(elementView.model);
graph.addCell(link);
connectMode = false;
$('#btn-connect').css('background','#fff');
}
});
// --- DODANE FUNKCJE KONIEC ---
// obwódka i znaczek usuwanie
function createToolsView() {
return new joint.dia.ToolsView({
tools: [
new joint.elementTools.Boundary({
padding: 20,
}),
new joint.elementTools.Remove({
offset: { x: -10, y: -10 },
focusOpacity: 0.5,
action: function() {
deleteShape();
}
})
]
});
graph.addCell(rect);
}
function createDiamond(paperX, paperY) {
const rect = new joint.shapes.standard.Rectangle({
const diamond = new joint.shapes.standard.Polygon({
position: { x: paperX - 24, y: paperY - 24 },
size: { width: 48, height: 48 },
angle: 45,
attrs: {
body: {
refPoints: '0.5,0 1,0.5 0.5,1 0,0.5', // Diamond shape points
fill: 'transparent',
stroke: '#000000',
strokeWidth: 1
@@ -74,7 +160,10 @@
}
}
});
graph.addCell(rect);
graph.addCell(diamond);
const elementView = diamond.findView(paper);
elementView.addTools(createToolsView());
}
function createEllipse(paperX, paperY) {
@@ -93,6 +182,8 @@
}
});
graph.addCell(rect);
const elementView = rect.findView(paper);
elementView.addTools(createToolsView());
}
function createText(paperX, paperY) {
@@ -113,6 +204,8 @@
}
});
graph.addCell(textBlock);
const elementView = textBlock.findView(paper);
elementView.addTools(createToolsView());
}
//create shape - END
@@ -148,6 +241,10 @@
const paperX = (x - translate.tx) / scale.sx;
const paperY = (y - translate.ty) / scale.sy;
activeElementView = null;
selectedElement = null;
hideResizeHandle();
if (shapeType === 'rectangle') {
createRectangle(paperX, paperY);
} else if (shapeType === 'ellipse') {
@@ -157,13 +254,134 @@
} else if (shapeType === 'text') {
createText(paperX, paperY);
}
const cells = graph.getCells();
if (cells.length > 0) {
const lastCell = cells[cells.length - 1];
const lastView = lastCell.findView(paper);
if (lastView && typeof lastView.hideTools === 'function') {
lastView.hideTools();
}
}
}
});
//init draggable - END
function getViewportCenter() {
const container = $('.canvas-container');
const scale = paper.scale();
const translate = paper.translate();
return {
x: (container.width() / 2 - translate.tx) / scale.sx,
y: (container.height() / 2 - translate.ty) / scale.sy
};
}
function copyShape() {
if (selectedElement) {
clipboard = selectedElement.clone();
}
}
function pasteShape() {
if (clipboard) {
const center = getViewportCenter();
const pasted = clipboard.clone();
pasted.position(center.x + 20, center.y + 20);
graph.addCell(pasted);
const pastedView = pasted.findView(paper);
if (pastedView) {
pastedView.addTools(createToolsView());
pastedView.showTools();
if (activeElementView && activeElementView !== pastedView) {
activeElementView.hideTools();
}
activeElementView = pastedView;
}
selectedElement = pasted;
showResizeHandle(pastedView);
}
}
function deleteShape() {
if (selectedElement) {
selectedElement.remove();
selectedElement = null;
activeElementView = null;
hideResizeHandle();
}
}
$('#btn-delete').click(function() {
deleteShape()
});
$('#btn-copy').click(function() {
copyShape();
});
$('#btn-paste').click(function() {
pasteShape();
});
paper.on('element:pointerclick', function (elementView) {
if (activeElementView && activeElementView !== elementView) {
activeElementView.hideTools();
}
elementView.showTools();
activeElementView = elementView;
selectedElement = elementView.model;
showResizeHandle(elementView);
});
let panStart = null;
paper.on('blank:pointerdown', function (evt) {
if (activeElementView) {
if (typeof activeElementView.hideTools === 'function') {
activeElementView.hideTools();
}
if (typeof activeElementView.unhighlight === 'function') {
activeElementView.unhighlight();
}
activeElementView = null;
}
selectedElement = null;
hideResizeHandle();
//ruszanie caloscia
panStart = {
x: evt.clientX,
y: evt.clientY,
tx: paper.translate().tx,
ty: paper.translate().ty
};
paper.$el.css('cursor', 'grabbing');
});
$(document).on('mousemove', function (evt) {
if (panStart) {
const dx = evt.clientX - panStart.x;
const dy = evt.clientY - panStart.y;
paper.translate(panStart.tx + dx, panStart.ty + dy);
showResizeHandle(activeElementView);
}
});
$(document).on('mouseup', function () {
if (panStart) {
panStart = null;
paper.$el.css('cursor', 'default');
}
});
//add text to text block - START
paper.on('element:pointerdblclick', function (elementView) {
paper.on('element:contextmenu', function (elementView,ev) {
ev.preventDefault();
if (elementView.model.attributes.type === 'standard.TextBlock') {
const currentText = elementView.model.attr('label/text');
const newText = prompt('Edytuj tekst:', currentText);
if (newText != null) {
@@ -171,8 +389,152 @@
}
}
});
$(document).on('keydown', function (e) {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'c') {
e.preventDefault();
copyShape();
return;
}
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'v') {
e.preventDefault();
pasteShape();
return;
}
if (e.key === 'Delete' && selectedElement) {
deleteShape();
}
});
//add text to text block - END
// resize
function showResizeHandle(elementView) {
if (!elementView) return;
const bbox = elementView.model.getBBox();
const clientRect = paper.localToClientRect(bbox);
const angle = elementView.model.get('angle') || 0;
$resizeHandle.css({
left: clientRect.x,
top: clientRect.y,
width: clientRect.width,
height: clientRect.height,
transform: `rotate(${angle}deg)`,
transformOrigin: 'center center'
}).show();
if ($resizeHandle.data('ui-resizable')) {
$resizeHandle.resizable('destroy');
}
let originalBBox;
$resizeHandle.resizable({
handles: 'n, e, s, w, ne, se, sw, nw',
start: function() {
if (activeElementView) {
activeElementView.hideTools();
}
originalBBox = selectedElement.getBBox();
},
resize: function(event, ui) {
if (selectedElement && originalBBox) {
const scale = paper.scale();
const newWidth = ui.size.width / scale.sx;
const newHeight = ui.size.height / scale.sy;
const newX = originalBBox.x + (ui.position.left - ui.originalPosition.left) / scale.sx;
const newY = originalBBox.y + (ui.position.top - ui.originalPosition.top) / scale.sy;
if (selectedElement.get('angle')) {
selectedElement.resize(newWidth, newHeight, { direction: 'center' });
} else {
selectedElement.position(newX, newY);
selectedElement.resize(newWidth, newHeight);
}
}
},
stop: function() {
if (activeElementView) {
activeElementView.showTools();
showResizeHandle(activeElementView);
}
originalBBox = null;
}
});
}
function hideResizeHandle() {
if ($resizeHandle.data('ui-resizable')) {
$resizeHandle.resizable('destroy');
}
$resizeHandle.hide();
}
//create shape - START
function createRectangle(paperX, paperY) {
const rect = new joint.shapes.standard.Rectangle({
position: { x: paperX - 40, y: paperY - 24 },
size: { width: 80, height: 48 },
attrs: {
body: {
fill: 'transparent',
stroke: '#000000',
strokeWidth: 1
},
label: {
text: ''
}
}
});
graph.addCell(rect);
const elementView = rect.findView(paper);
elementView.addTools(createToolsView());
}
// Zoom
const zoomStep = 0.2;
const minScale = 0.2;
const maxScale = 3.0;
function applyZoom(newScale, ox, oy) {
paper.scale(newScale, newScale, ox, oy);
showResizeHandle(activeElementView);
}
$('#zoom-in').click(function () {
const currentScale = paper.scale().sx;
const newScale = Math.min(maxScale, currentScale + zoomStep);
applyZoom(newScale);
});
$('#zoom-out').click(function () {
const currentScale = paper.scale().sx;
const newScale = Math.max(minScale, currentScale - zoomStep);
applyZoom(newScale);
});
// Zoom za pomoca kolka myszy
paper.on('blank:mousewheel cell:mousewheel', function (evt, x, y, delta) {
evt.preventDefault();
const oldScale = paper.scale().sx;
const zoomFactor = 0.2;
let newScale = oldScale * (1 + delta * zoomFactor);
newScale = Math.max(minScale, Math.min(maxScale, newScale));
applyZoom(newScale, x, y);
});
});
</script>
</body>

39629
joint.js Normal file

File diff suppressed because it is too large Load Diff

333
style.css
View File

@@ -1,71 +1,278 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.shape-wrapper {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, auto);
gap: 30px 10px;
padding-bottom: 40px;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
min-height: 100vh;
overflow: hidden;
}
#shape-rectangle {
width: 80px;
height: 48px;
}
.toolbar {
background: #ffffff;
color: #333;
padding: 20px;
display: flex;
flex-direction: column;
gap: 16px;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
position: absolute;
width: 280px;
left: 0;
top: 0;
height: 100vh;
z-index: 9;
overflow-y: auto;
border-right: 1px solid #e0e0e0;
}
#shape-ellipse {
width: 80px;
height: 48px;
border-radius: 50%;
}
.toolbar p {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #666;
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 2px solid #e0e0e0;
}
#shape-diamond {
height: 48px;
width: 48px;
transform: rotate(45deg) translateX(15px);
}
.toolbar-wrapper {
background: #f9f9f9;
border-radius: 8px;
border: 1px solid #e0e0e0;
}
#shape-text {
width: 80px;
height: 48px;
border-style: dashed;
color: #ffffff;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
}
.shape-wrapper {
display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 12px;
min-height: 152px;
justify-content: space-between;
align-content: space-between;
}
.shape {
background: transparent;
border: 1px solid white;
}
.shape {
background: #fff;
border: 2px solid #d0d0d0;
cursor: grab;
transition: border-color 0.2s, box-shadow 0.2s;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.toolbar {
background: #3498db;
color: white;
padding: 20px;
display: flex;
flex-wrap: wrap;
flex-direction: column;
gap: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
position: absolute;
width: 300px;
left: 0px;
top: 0px;
height: 100vh;
z-index: 9;
border-top-right-radius: 30px;
border-bottom-right-radius: 30px;
}
.shape:hover {
border-color: #666;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15);
}
.toolbar-wrapper {
padding-bottom: 30px;
padding: 20px;
}
.shape:active {
cursor: grabbing;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
#shape-rectangle {
width: 80px;
height: 48px;
border-radius: 4px;
}
#shape-ellipse {
width: 80px;
height: 48px;
border-radius: 50%;
}
#shape-diamond {
height: 48px;
width: 48px;
transform: rotate(45deg) translate(3px, -16px);
border-radius: 4px;
}
#shape-text {
width: 80px;
height: 48px;
border-style: dashed;
border-radius: 4px;
color: #666;
font-size: 12px;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
}
/* Tools wrapper */
.tools-wrapper {
display: flex;
flex-direction: column;
gap: 8px;
padding: 0;
}
.tools-wrapper button {
padding: 12px 16px;
font-size: 13px;
font-weight: 500;
border: 1px solid #d0d0d0;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s, border-color 0.2s;
background: #fff;
color: #333;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.tools-wrapper button:hover {
background: #f5f5f5;
border-color: #999;
}
.tools-wrapper button:active {
background: #e8e8e8;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
#btn-delete {
color: #d32f2f;
border-color: #d32f2f;
}
#btn-delete:hover {
background: #ffebee;
border-color: #c62828;
}
#btn-copy {
color: #1976d2;
border-color: #1976d2;
}
#btn-copy:hover {
background: #e3f2fd;
border-color: #1565c0;
}
#btn-paste {
color: #388e3c;
border-color: #388e3c;
}
#btn-paste:hover {
background: #e8f5e9;
border-color: #2e7d32;
}
/* Zoom wrapper */
.zoom-wrapper {
display: flex;
justify-content: center;
gap: 12px;
padding: 6px;
}
.zoom-wrapper button {
background: #fff;
color: #333;
border: 1px solid #d0d0d0;
border-radius: 6px;
width: 50px;
height: 50px;
font-size: 20px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: background 0.2s, border-color 0.2s;
outline: none;
}
.zoom-wrapper button:hover {
background: #f5f5f5;
border-color: #999;
}
.zoom-wrapper button:active {
background: #e8e8e8;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
/* Canvas container */
.canvas-container {
position: absolute;
left: 280px;
top: 0;
right: 0;
bottom: 0;
background: #ffffff;
background-image:
linear-gradient(rgba(0, 0, 0, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 0, 0, 0.03) 1px, transparent 1px);
background-size: 20px 20px;
overflow: auto;
}
#paper {
width: 100%;
height: 100%;
}
/* Scrollbar styling */
.toolbar::-webkit-scrollbar {
width: 6px;
}
.toolbar::-webkit-scrollbar-track {
background: #f5f5f5;
}
.toolbar::-webkit-scrollbar-thumb {
background: #d0d0d0;
border-radius: 3px;
}
.toolbar::-webkit-scrollbar-thumb:hover {
background: #999;
}
#resize-handle {
position: absolute;
box-sizing: border-box;
pointer-events: auto;
}
/* Responsive */
@media (max-width: 768px) {
.toolbar {
width: 100%;
height: auto;
border-radius: 0;
}
.canvas-container {
left: 0;
top: auto;
}
}
/* color wrapper */
.color-wrapper{
display: flex;
flex-direction: column;
position: relative;
}
#color-palette{
display:none;
top: 41px;
}
#color-palette.active{
display: grid!important;
}