Improve splitter UX: visual feedback & smoother resizing
Enhanced the editor's vertical splitter with immediate visual feedback during dragging, including dynamic width, shadow, and handle size. Updated logic to re-render UI and save split ratio in real time. Refined save status updates to reduce unnecessary renders. Improved mobile responsiveness and overall user experience.
This commit is contained in:
@@ -82,7 +82,7 @@
|
|||||||
<!-- Simple Splitter -->
|
<!-- Simple Splitter -->
|
||||||
@if (showPreview)
|
@if (showPreview)
|
||||||
{
|
{
|
||||||
<div class="splitter" @onmousedown="OnSplitterMouseDown" @onmousedown:preventDefault="true">
|
<div class="splitter @(isDragging ? "dragging" : "")" @onmousedown="OnSplitterMouseDown" @onmousedown:preventDefault="true">
|
||||||
<div class="splitter-handle"></div>
|
<div class="splitter-handle"></div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -156,11 +156,11 @@
|
|||||||
await InvokeAsync(async () => await AutoSave());
|
await InvokeAsync(async () => await AutoSave());
|
||||||
}, null, 2000, Timeout.Infinite); // Increased to 2 seconds for production
|
}, null, 2000, Timeout.Infinite); // Increased to 2 seconds for production
|
||||||
|
|
||||||
// Don't show "Saving..." immediately, wait for actual save
|
// Only update status if it was previously "Saved"
|
||||||
if (saveStatus == "Saved")
|
if (saveStatus == "Saved")
|
||||||
{
|
{
|
||||||
saveStatus = "Modified";
|
saveStatus = "Modified";
|
||||||
await InvokeAsync(StateHasChanged);
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,9 +177,11 @@
|
|||||||
if (string.IsNullOrEmpty(sessionId) || !hasUnsavedChanges)
|
if (string.IsNullOrEmpty(sessionId) || !hasUnsavedChanges)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var previousStatus = saveStatus;
|
||||||
|
|
||||||
// Set saving status only when actually starting to save
|
// Set saving status only when actually starting to save
|
||||||
saveStatus = "Saving...";
|
saveStatus = "Saving...";
|
||||||
await InvokeAsync(StateHasChanged);
|
StateHasChanged(); // Use StateHasChanged instead of InvokeAsync
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -194,7 +196,7 @@
|
|||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
saveStatus = "Timeout";
|
saveStatus = "Timeout";
|
||||||
// Retry after 5 seconds
|
// Simplified retry - no immediate re-rendering
|
||||||
_ = Task.Delay(5000).ContinueWith(async _ =>
|
_ = Task.Delay(5000).ContinueWith(async _ =>
|
||||||
{
|
{
|
||||||
if (hasUnsavedChanges)
|
if (hasUnsavedChanges)
|
||||||
@@ -206,7 +208,7 @@
|
|||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
saveStatus = "Error";
|
saveStatus = "Error";
|
||||||
// Retry after 3 seconds
|
// Simplified retry - no immediate re-rendering
|
||||||
_ = Task.Delay(3000).ContinueWith(async _ =>
|
_ = Task.Delay(3000).ContinueWith(async _ =>
|
||||||
{
|
{
|
||||||
if (hasUnsavedChanges)
|
if (hasUnsavedChanges)
|
||||||
@@ -216,7 +218,11 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
// Only trigger re-render if status actually changed
|
||||||
|
if (saveStatus != previousStatus)
|
||||||
|
{
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TogglePreview()
|
private void TogglePreview()
|
||||||
@@ -396,25 +402,33 @@
|
|||||||
{
|
{
|
||||||
if (!isDragging || containerWidth <= 0) return;
|
if (!isDragging || containerWidth <= 0) return;
|
||||||
|
|
||||||
var newRatio = clientX / containerWidth;
|
var newRatio = Math.Max(0.2, Math.Min(0.8, clientX / containerWidth)); // Clamp between 20% and 80%
|
||||||
splitRatio = Math.Max(0.2, Math.Min(0.8, newRatio)); // Clamp between 20% and 80%
|
|
||||||
|
|
||||||
// Save to localStorage
|
// Update immediately for smooth dragging
|
||||||
|
splitRatio = newRatio;
|
||||||
|
|
||||||
|
// Use StateHasChanged for immediate visual feedback
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public async Task StopDragging()
|
||||||
|
{
|
||||||
|
if (!isDragging) return;
|
||||||
|
|
||||||
|
isDragging = false;
|
||||||
|
|
||||||
|
// Save split ratio to localStorage when dragging stops
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await JSRuntime.InvokeVoidAsync("localStorage.setItem", "notemark-split-ratio", splitRatio.ToString());
|
await JSRuntime.InvokeVoidAsync("localStorage.setItem", "notemark-split-ratio", splitRatio.ToString("F2"));
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// Ignore localStorage errors
|
// Ignore localStorage errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Single StateHasChanged at the end
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable]
|
|
||||||
public void StopDragging()
|
|
||||||
{
|
|
||||||
isDragging = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -438,11 +438,18 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
transition: background-color 0.2s ease;
|
transition: all 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitter:hover {
|
.splitter:hover {
|
||||||
background-color: var(--accent);
|
background-color: var(--accent);
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitter.dragging {
|
||||||
|
background-color: var(--accent);
|
||||||
|
width: 12px;
|
||||||
|
box-shadow: 0 0 8px rgba(0, 122, 204, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitter-handle {
|
.splitter-handle {
|
||||||
@@ -450,11 +457,19 @@
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
background-color: var(--secondary-text);
|
background-color: var(--secondary-text);
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
transition: background-color 0.2s ease;
|
transition: all 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitter:hover .splitter-handle {
|
.splitter:hover .splitter-handle {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
width: 3px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitter.dragging .splitter-handle {
|
||||||
|
background-color: white;
|
||||||
|
width: 4px;
|
||||||
|
height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile Responsive - Place at end to override desktop styles */
|
/* Mobile Responsive - Place at end to override desktop styles */
|
||||||
|
|||||||
@@ -59,9 +59,15 @@ window.startSplitterDrag = function(blazorInstance) {
|
|||||||
document.addEventListener('mousemove', handleSplitterMove);
|
document.addEventListener('mousemove', handleSplitterMove);
|
||||||
document.addEventListener('mouseup', handleSplitterUp);
|
document.addEventListener('mouseup', handleSplitterUp);
|
||||||
|
|
||||||
// Prevent text selection
|
// Improve visual feedback during drag
|
||||||
document.body.style.userSelect = 'none';
|
document.body.style.userSelect = 'none';
|
||||||
document.body.style.cursor = 'col-resize';
|
document.body.style.cursor = 'col-resize';
|
||||||
|
|
||||||
|
// Add dragging class for visual feedback
|
||||||
|
const splitter = document.querySelector('.splitter');
|
||||||
|
if (splitter) {
|
||||||
|
splitter.classList.add('dragging');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleSplitterMove(e) {
|
function handleSplitterMove(e) {
|
||||||
@@ -88,6 +94,12 @@ function handleSplitterUp(e) {
|
|||||||
document.body.style.cursor = '';
|
document.body.style.cursor = '';
|
||||||
document.body.style.userSelect = '';
|
document.body.style.userSelect = '';
|
||||||
|
|
||||||
|
// Remove dragging visual state
|
||||||
|
const splitter = document.querySelector('.splitter');
|
||||||
|
if (splitter) {
|
||||||
|
splitter.classList.remove('dragging');
|
||||||
|
}
|
||||||
|
|
||||||
// Notify Blazor that dragging stopped
|
// Notify Blazor that dragging stopped
|
||||||
if (splitterState.blazorInstance) {
|
if (splitterState.blazorInstance) {
|
||||||
splitterState.blazorInstance.invokeMethodAsync('StopDragging');
|
splitterState.blazorInstance.invokeMethodAsync('StopDragging');
|
||||||
|
|||||||
Reference in New Issue
Block a user