diff --git a/README.md b/README.md index b5215fd..b258db0 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Markup language for rendering network protocol diagrams. ## Interactive Demo -Try the live editor [here](https://alieron.github.io/protocol-ml/) +Try the [stable live editor](https://yeeshin504.github.io/protocol-ml/) or the [development live editor](https://yeeshin504.github.io/protocol-ml/dev/). ### Development diff --git a/docs/editor.css b/docs/editor.css index 0f01077..d089dbd 100644 --- a/docs/editor.css +++ b/docs/editor.css @@ -18,6 +18,79 @@ header { border-bottom: 1px solid #3e3e42; } +.header-content { + display: flex; + justify-content: space-between; + align-items: center; +} + +.resizer { + width: 6px; + background: #1e1e1e; + cursor: col-resize; + position: relative; + z-index: 100; + border-left: 1px solid #3e3e42; + border-right: 1px solid #3e3e42; + transition: background 0.2s; +} + +.resizer:hover, .resizer.dragging { + background: #0e639c; +} + +.toggle-arrow { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: #2d2d2d; + border: 1px solid #3e3e42; + color: #858585; + width: 20px; + height: 40px; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 10px; + border-radius: 4px; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); + transition: all 0.2s; +} + +.toggle-arrow:hover { + background: #3e3e42; + color: #ffffff; + border-color: #0e639c; +} + +.panel { + display: flex; + flex-direction: column; + min-width: 0; +} + +#panel-reference { + flex: 0 0 280px; + background: #252526; + transition: flex-basis 0.3s ease-in-out; +} + +#panel-reference.collapsed { + flex: 0 0 0px !important; + overflow: hidden; +} + +#panel-editor { + flex: 1 1 0; +} + +#panel-preview { + flex: 1 1 0; +} + h1 { font-size: 1.5rem; font-weight: 600; @@ -35,11 +108,102 @@ main { height: calc(100vh - 80px); } -.panel { - flex: 1; - display: flex; - flex-direction: column; - border-right: 1px solid #3e3e42; +.reference-content { + background: #252526; + padding: 1.25rem; + overflow-y: auto; +} + +.search-container { + padding: 0.75rem 1.25rem; + background: #252526; + border-bottom: 1px solid #3e3e42; +} + +#refSearch { + width: 100%; + background: #3c3c3c; + border: 1px solid #3e3e42; + color: #cccccc; + padding: 0.4rem 0.75rem; + border-radius: 3px; + font-size: 0.85rem; + outline: none; + transition: border-color 0.2s; +} + +#refSearch:focus { + border-color: #0e639c; +} + +.loading { + color: #858585; + font-size: 0.875rem; + font-style: italic; +} + +.ref-group h4 { + color: #569cd6; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; + margin: 1.5rem 0 0.75rem 0; +} + +.ref-group:first-child h4 { + margin-top: 0; +} + +.ref-item { + background: #2d2d2d; + border: 1px solid #3e3e42; + border-radius: 6px; + padding: 0.75rem; + margin-bottom: 0.75rem; + transition: border-color 0.2s; +} + +.ref-item:hover { + border-color: #454545; +} + +.ref-title { + font-size: 0.85rem; + color: #cccccc; + font-weight: 500; + margin-bottom: 0.4rem; +} + +.ref-syntax { + display: block; + font-family: 'JetBrains Mono', 'Consolas', 'Monaco', monospace; + font-size: 0.75rem; + font-variant-ligatures: normal; + background: #1e1e1e; + padding: 0.4rem 0.6rem; + color: #ce9178; + border-radius: 4px; + margin-bottom: 0.6rem; + border: 1px solid #3e3e42; +} + +.try-btn-small { + background: transparent; + border: 1px solid #3e3e42; + color: #858585; + padding: 0.25rem 0.6rem; + border-radius: 3px; + font-size: 0.7rem; + cursor: pointer; + width: 100%; + transition: all 0.2s; +} + +.try-btn-small:hover { + background: #3e3e42; + color: #ffffff; + border-color: #0e639c; } .panel:last-child { @@ -108,12 +272,16 @@ main { } .protocol-ml-wrapper { - display: inline-block; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; } svg { max-width: 100%; - height: auto; + max-height: 100%; } @media (max-width: 768px) { @@ -170,7 +338,10 @@ svg { color: #b5cea8; } -/* identifiers */ +/* identifiers/aliases */ +.cm-pml-alias { + color: #4ec9b0; +} .cm-pml-arrow-normal { color: #4fc1ff; font-weight: bold; @@ -214,4 +385,36 @@ svg { .cm-pml-comment { color: #6a9955; font-style: italic; +} + +/* CodeMirror Hint Theme */ +.CodeMirror-hints { + position: absolute; + z-index: 10; + overflow: hidden; + list-style: none; + margin: 0; + padding: 2px; + background: #252526; + border: 1px solid #454545; + border-radius: 4px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); + font-family: 'JetBrains Mono', 'Consolas', 'Monaco', monospace; + font-size: 13px; + color: #d4d4d4; + max-height: 200px; + overflow-y: auto; +} + +.CodeMirror-hint { + margin: 0; + padding: 4px 8px; + border-radius: 2px; + cursor: pointer; + color: #d4d4d4; +} + +li.CodeMirror-hint-active { + background: #094771; + color: white; } \ No newline at end of file diff --git a/docs/editor.js b/docs/editor.js index 68e6fe7..55674a0 100644 --- a/docs/editor.js +++ b/docs/editor.js @@ -6,8 +6,206 @@ const copyCodeBtn = document.getElementById('copyCode'); const copyPNGBtn = document.getElementById('copyPNG'); const copySVGBtn = document.getElementById('copySVG'); -const INITIAL_CODE = `def messageSpacing 20px -def participantSpacing 160px +// Panel & Resizer elements +const panelRef = document.getElementById('panel-reference'); +const panelEditor = document.getElementById('panel-editor'); +const panelPreview = document.getElementById('panel-preview'); +const resizerRef = document.getElementById('resizer-ref'); +const resizerPreview = document.getElementById('resizer-preview'); +const toggleRefBtn = document.getElementById('toggleReference'); + +// Resizing logic +function initResizer(resizer, leftPanel, rightPanel, isFirst) { + let startX, startWidthLeft, startWidthRight; + + resizer.addEventListener('mousedown', (e) => { + if (e.target.id === 'toggleReference') return; + + startX = e.clientX; + startWidthLeft = leftPanel.offsetWidth; + startWidthRight = rightPanel.offsetWidth; + + // Disable transitions during resizing to prevent lag + leftPanel.style.transition = 'none'; + + const onMouseMove = (e) => { + const deltaX = e.clientX - startX; + if (isFirst) { + const newWidth = Math.max(0, startWidthLeft + deltaX); + leftPanel.style.flex = `0 0 ${newWidth}px`; + if (editor) editor.refresh(); + } else { + const newWidth = Math.max(100, startWidthLeft + deltaX); + leftPanel.style.flex = `0 0 ${newWidth}px`; + if (editor) editor.refresh(); + } + }; + + const onMouseUp = () => { + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + resizer.classList.remove('dragging'); + document.body.style.cursor = 'default'; + + // Re-enable transition after resizing + leftPanel.style.transition = ''; + + if (editor) editor.refresh(); + }; + + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp); + resizer.classList.add('dragging'); + document.body.style.cursor = 'col-resize'; + }); +} + +// Toggle Reference logic +toggleRefBtn.addEventListener('click', (e) => { + e.stopPropagation(); + panelRef.classList.toggle('collapsed'); + const isCollapsed = panelRef.classList.contains('collapsed'); + toggleRefBtn.textContent = isCollapsed ? '▶' : '◀'; +}); + +const REFERENCE_DATA = [ + { + group: "Participants", + items: [ + { title: "Define Participant", syntax: 'participant Name alias', example: 'participant Client c\nparticipant Server s' }, + { title: "Participant Spacing", syntax: 'def participantSpacing 240px', example: 'def participantSpacing 300px' }, + { title: "Label Height", syntax: 'def participantLabelHeight 30px', example: 'def participantLabelHeight 50px' }, + { title: "Font Size", syntax: 'def participantFontSize 20px', example: 'def participantFontSize 24px' } + ] + }, + { + group: "Message Arrows", + items: [ + { title: "Normal Arrow", syntax: 'a -> b : "label"', example: 'a -> b : "Request"' }, + { title: "Thick Arrow", syntax: 'a => b : "label"', example: 'a => b : "Big Data"' }, + { title: "Corrupt Arrow", syntax: 'a ~> b : "label"', example: 'a ~> b : "Fragmented"' }, + { title: "Dropped Message", syntax: 'a -x b : "label"', example: 'a -x b : "Timeout"' }, + { title: "Time Offsets", syntax: 'a @1.5 -> b @1', example: 'a @1.5 -> b @1 : "Relative Time"' } + ] + }, + { + group: "Arrow Styles", + items: [ + { title: "Arrow Head Size", syntax: 'def arrowHeadSize 10px', example: 'def arrowHeadSize 15px' }, + { title: "Thick Thickness", syntax: 'def thickArrowThickness 40px', example: 'def thickArrowThickness 20px' }, + { title: "Label Offset", syntax: 'def labelOffset 10px', example: 'def labelOffset 20px' }, + { title: "Message Font Size", syntax: 'def messageFontSize 15px', example: 'def messageFontSize 18px' } + ] + }, + { + group: "Corrupt & Dropped", + items: [ + { title: "Corrupt Start %", syntax: 'def corruptStartRatio 85%', example: 'def corruptStartRatio 50%' }, + { title: "Drop Start %", syntax: 'def dropStartRatio 85%', example: 'def dropStartRatio 50%' }, + { title: "Drop Cross Size", syntax: 'def dropCrossSize 12px', example: 'def dropCrossSize 20px' }, + { title: "Corrupt Squiggle Size", syntax: 'def squiggleSize 20px', example: 'def squiggleSize 10px' }, + { title: "Corrupt Squiggle Count", syntax: 'def squiggleCount 2', example: 'def squiggleCount 5' } + ] + }, + { + group: "Annotations", + items: [ + { title: "Left Note", syntax: 'a < "text"', example: 'a < "Local check"' }, + { title: "Right Note", syntax: 'a > "text"', example: 'a > "Processing"' }, + { title: "Annotation Width", syntax: 'def annotationWidth 80px', example: 'def annotationWidth 120px' } + ] + }, + { + group: "Grid & Time", + items: [ + { title: "Show Grid", syntax: 'def showGrid true', example: 'def showGrid true' }, + { title: "Show Time Ticks", syntax: 'def showTimeTicks true', example: 'def showTimeTicks true' }, + { title: "Time Interval", syntax: 'def timeTickInterval 40px', example: 'def timeTickInterval 60px' }, + { title: "Time Unit", syntax: 'def timeUnit /ms', example: 'def timeUnit /ms' } + ] + }, + { + group: "Layout", + items: [ + { title: "Horizontal Padding", syntax: 'def paddingX 30px', example: 'def paddingX 50px' }, + { title: "Vertical Padding", syntax: 'def paddingY 20px', example: 'def paddingY 50px' } + ] + } +]; + +function renderReference(filter = '') { + const container = document.getElementById('reference-list'); + if (!container) return; + + container.innerHTML = ''; // Clear loading message + const query = filter.toLowerCase().trim(); + + REFERENCE_DATA.forEach(group => { + const filteredItems = group.items.filter(item => + item.title.toLowerCase().includes(query) || + item.syntax.toLowerCase().includes(query) || + group.group.toLowerCase().includes(query) + ); + + if (filteredItems.length === 0) return; + + const groupEl = document.createElement('div'); + groupEl.className = 'ref-group'; + groupEl.innerHTML = `
Interactive Protocol Diagram Editor
+Interactive Protocol Diagram Editor
+