feat(workspace): add resizable markdown editor/preview split with draggable handle and markdown outlet styling/demo
This commit is contained in:
@@ -6,7 +6,8 @@ import {
|
||||
type TemplateResult,
|
||||
css,
|
||||
cssManager,
|
||||
domtools
|
||||
domtools,
|
||||
state,
|
||||
} from '@design.estate/dees-element';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
import { DeesWorkspaceMonaco } from '../dees-workspace-monaco/dees-workspace-monaco.js';
|
||||
@@ -27,31 +28,113 @@ export class DeesWorkspaceMarkdown extends DeesElement {
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||
.gridcontainer {
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.splitContainer {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 60% 40%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.editorContainer {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-width: 100px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.resizeHandle {
|
||||
width: 6px;
|
||||
height: 100%;
|
||||
background: ${cssManager.bdTheme('#e5e7eb', '#30363d')};
|
||||
cursor: col-resize;
|
||||
flex-shrink: 0;
|
||||
transition: background 0.15s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.resizeHandle:hover,
|
||||
.resizeHandle.dragging {
|
||||
background: ${cssManager.bdTheme('#3b82f6', '#58a6ff')};
|
||||
}
|
||||
|
||||
.resizeHandle::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 2px;
|
||||
height: 32px;
|
||||
background: ${cssManager.bdTheme('#9ca3af', '#6e7681')};
|
||||
border-radius: 1px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.resizeHandle:hover::after,
|
||||
.resizeHandle.dragging::after {
|
||||
background: ${cssManager.bdTheme('#ffffff', '#ffffff')};
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.outletContainer {
|
||||
background: #111;
|
||||
color: #fff;
|
||||
font-family: 'Roboto Slab';
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-width: 100px;
|
||||
background: ${cssManager.bdTheme('#ffffff', '#0d1117')};
|
||||
color: ${cssManager.bdTheme('#24292f', '#e6edf3')};
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
||||
padding: 20px;
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Prevent text selection while dragging */
|
||||
.splitContainer.dragging {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.splitContainer.dragging .editorContainer,
|
||||
.splitContainer.dragging .outletContainer {
|
||||
pointer-events: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
/**
|
||||
* Initial split ratio for the editor (left) panel.
|
||||
* Value from 0 to 100 representing percentage width.
|
||||
* Default is 50 (50/50 split).
|
||||
*/
|
||||
@property({ type: Number })
|
||||
accessor splitRatio: number = 50;
|
||||
|
||||
/**
|
||||
* Minimum width percentage for either panel.
|
||||
*/
|
||||
@property({ type: Number })
|
||||
accessor minPanelSize: number = 10;
|
||||
|
||||
@state()
|
||||
accessor currentSplitRatio: number = 50;
|
||||
|
||||
@state()
|
||||
accessor isDragging: boolean = false;
|
||||
|
||||
private resizeHandleElement: HTMLElement;
|
||||
private containerElement: HTMLElement;
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<div class="gridcontainer">
|
||||
<div class="editorContainer">
|
||||
<div class="splitContainer ${this.isDragging ? 'dragging' : ''}">
|
||||
<div class="editorContainer" style="width: ${this.currentSplitRatio}%">
|
||||
<dees-workspace-monaco
|
||||
.language=${'markdown'}
|
||||
.content=${`# a test content
|
||||
@@ -78,7 +161,11 @@ const hello = 'yes'
|
||||
wordWrap="bounded"
|
||||
></dees-workspace-monaco>
|
||||
</div>
|
||||
<div class="outletContainer">
|
||||
<div
|
||||
class="resizeHandle ${this.isDragging ? 'dragging' : ''}"
|
||||
@mousedown=${this.handleMouseDown}
|
||||
></div>
|
||||
<div class="outletContainer" style="width: ${100 - this.currentSplitRatio}%">
|
||||
<dees-workspace-markdownoutlet></dees-workspace-markdownoutlet>
|
||||
</div>
|
||||
</div>
|
||||
@@ -87,9 +174,17 @@ const hello = 'yes'
|
||||
|
||||
public async firstUpdated(_changedPropertiesArg) {
|
||||
await super.firstUpdated(_changedPropertiesArg);
|
||||
|
||||
// Initialize current ratio from property
|
||||
this.currentSplitRatio = this.splitRatio;
|
||||
|
||||
// Cache elements
|
||||
this.containerElement = this.shadowRoot.querySelector('.splitContainer');
|
||||
this.resizeHandleElement = this.shadowRoot.querySelector('.resizeHandle');
|
||||
|
||||
const editor = this.shadowRoot.querySelector('dees-workspace-monaco') as DeesWorkspaceMonaco;
|
||||
|
||||
// lets care about wiring the markdown stuff.
|
||||
// Wire up markdown rendering
|
||||
const markdownOutlet = this.shadowRoot.querySelector('dees-workspace-markdownoutlet');
|
||||
const smartmarkdownInstance = new domtools.plugins.smartmarkdown.SmartMarkdown();
|
||||
const mdParsedResult = await smartmarkdownInstance.getMdParsedResultFromMarkdown('loading...')
|
||||
@@ -97,6 +192,64 @@ const hello = 'yes'
|
||||
await mdParsedResult.updateFromMarkdownString(contentArg)
|
||||
const html = mdParsedResult.html;
|
||||
markdownOutlet.updateHtmlText(html);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
private handleMouseDown = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
this.isDragging = true;
|
||||
|
||||
document.addEventListener('mousemove', this.handleMouseMove);
|
||||
document.addEventListener('mouseup', this.handleMouseUp);
|
||||
};
|
||||
|
||||
private handleMouseMove = (e: MouseEvent) => {
|
||||
if (!this.isDragging || !this.containerElement) return;
|
||||
|
||||
const containerRect = this.containerElement.getBoundingClientRect();
|
||||
const containerWidth = containerRect.width;
|
||||
const mouseX = e.clientX - containerRect.left;
|
||||
|
||||
// Calculate percentage, accounting for the resize handle width (6px)
|
||||
let newRatio = (mouseX / containerWidth) * 100;
|
||||
|
||||
// Clamp to min/max
|
||||
newRatio = Math.max(this.minPanelSize, Math.min(100 - this.minPanelSize, newRatio));
|
||||
|
||||
this.currentSplitRatio = newRatio;
|
||||
};
|
||||
|
||||
private handleMouseUp = () => {
|
||||
this.isDragging = false;
|
||||
document.removeEventListener('mousemove', this.handleMouseMove);
|
||||
document.removeEventListener('mouseup', this.handleMouseUp);
|
||||
|
||||
// Trigger resize on monaco editor
|
||||
const editor = this.shadowRoot.querySelector('dees-workspace-monaco') as DeesWorkspaceMonaco;
|
||||
if (editor) {
|
||||
// Monaco needs to be notified of size changes
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}
|
||||
};
|
||||
|
||||
async disconnectedCallback() {
|
||||
await super.disconnectedCallback();
|
||||
// Clean up event listeners
|
||||
document.removeEventListener('mousemove', this.handleMouseMove);
|
||||
document.removeEventListener('mouseup', this.handleMouseUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Programmatically set the split ratio
|
||||
*/
|
||||
public setSplitRatio(ratio: number) {
|
||||
this.currentSplitRatio = Math.max(this.minPanelSize, Math.min(100 - this.minPanelSize, ratio));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset to initial split ratio
|
||||
*/
|
||||
public resetSplitRatio() {
|
||||
this.currentSplitRatio = this.splitRatio;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user