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 -->
|
||||
@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>
|
||||
}
|
||||
@@ -156,11 +156,11 @@
|
||||
await InvokeAsync(async () => await AutoSave());
|
||||
}, 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")
|
||||
{
|
||||
saveStatus = "Modified";
|
||||
await InvokeAsync(StateHasChanged);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,9 +177,11 @@
|
||||
if (string.IsNullOrEmpty(sessionId) || !hasUnsavedChanges)
|
||||
return;
|
||||
|
||||
var previousStatus = saveStatus;
|
||||
|
||||
// Set saving status only when actually starting to save
|
||||
saveStatus = "Saving...";
|
||||
await InvokeAsync(StateHasChanged);
|
||||
StateHasChanged(); // Use StateHasChanged instead of InvokeAsync
|
||||
|
||||
try
|
||||
{
|
||||
@@ -194,7 +196,7 @@
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
saveStatus = "Timeout";
|
||||
// Retry after 5 seconds
|
||||
// Simplified retry - no immediate re-rendering
|
||||
_ = Task.Delay(5000).ContinueWith(async _ =>
|
||||
{
|
||||
if (hasUnsavedChanges)
|
||||
@@ -206,7 +208,7 @@
|
||||
catch (Exception)
|
||||
{
|
||||
saveStatus = "Error";
|
||||
// Retry after 3 seconds
|
||||
// Simplified retry - no immediate re-rendering
|
||||
_ = Task.Delay(3000).ContinueWith(async _ =>
|
||||
{
|
||||
if (hasUnsavedChanges)
|
||||
@@ -216,7 +218,11 @@
|
||||
});
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
// Only trigger re-render if status actually changed
|
||||
if (saveStatus != previousStatus)
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void TogglePreview()
|
||||
@@ -396,25 +402,33 @@
|
||||
{
|
||||
if (!isDragging || containerWidth <= 0) return;
|
||||
|
||||
var newRatio = clientX / containerWidth;
|
||||
splitRatio = Math.Max(0.2, Math.Min(0.8, newRatio)); // Clamp between 20% and 80%
|
||||
var newRatio = Math.Max(0.2, Math.Min(0.8, clientX / containerWidth)); // 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
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("localStorage.setItem", "notemark-split-ratio", splitRatio.ToString());
|
||||
await JSRuntime.InvokeVoidAsync("localStorage.setItem", "notemark-split-ratio", splitRatio.ToString("F2"));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore localStorage errors
|
||||
}
|
||||
|
||||
// Single StateHasChanged at the end
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public void StopDragging()
|
||||
{
|
||||
isDragging = false;
|
||||
}
|
||||
}
|
||||
@@ -438,11 +438,18 @@
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
transition: background-color 0.2s ease;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.splitter:hover {
|
||||
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 {
|
||||
@@ -450,11 +457,19 @@
|
||||
height: 40px;
|
||||
background-color: var(--secondary-text);
|
||||
border-radius: 1px;
|
||||
transition: background-color 0.2s ease;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.splitter:hover .splitter-handle {
|
||||
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 */
|
||||
|
||||
@@ -59,9 +59,15 @@ window.startSplitterDrag = function(blazorInstance) {
|
||||
document.addEventListener('mousemove', handleSplitterMove);
|
||||
document.addEventListener('mouseup', handleSplitterUp);
|
||||
|
||||
// Prevent text selection
|
||||
// Improve visual feedback during drag
|
||||
document.body.style.userSelect = 'none';
|
||||
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) {
|
||||
@@ -88,6 +94,12 @@ function handleSplitterUp(e) {
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
|
||||
// Remove dragging visual state
|
||||
const splitter = document.querySelector('.splitter');
|
||||
if (splitter) {
|
||||
splitter.classList.remove('dragging');
|
||||
}
|
||||
|
||||
// Notify Blazor that dragging stopped
|
||||
if (splitterState.blazorInstance) {
|
||||
splitterState.blazorInstance.invokeMethodAsync('StopDragging');
|
||||
|
||||
Reference in New Issue
Block a user