This commit is contained in:
2025-09-02 23:30:14 +10:00
parent 4ddca1324a
commit 5c893d7ce7

View File

@@ -1,7 +1,19 @@
<script lang="ts"> <script lang="ts">
import { Input } from "$lib/components/ui/input";
import { onMount } from "svelte"; import { onMount } from "svelte";
const socket = new WebSocket("ws://localhost:7050");
socket.onmessage = (event) => {
const message = event.data;
console.log("Received message from server:", message);
};
socket.onopen = () => {
console.log("WebSocket connection established.");
};
// Connection opened
// --- Configuration --- // --- Configuration ---
const numRows = 1000; // Increased to show performance const numRows = 1000; // Increased to show performance
const numCols = 100; // Increased to show performance const numCols = 100; // Increased to show performance
@@ -52,6 +64,23 @@
} }
} }
function handleCellBlur(row: number, col: number) {
// This function runs when a cell loses focus.
// You can add your logic here, e.g., validation, calculations, etc.
console.log(
`Cell (${row + 1}, ${columnLabels[col]}) lost focus. Value:`,
gridData[row][col],
);
socket.send(
JSON.stringify({
row: row + 1,
col: columnLabels[col],
value: gridData[row][col],
}),
);
}
// --- Reactive Calculations for Virtualization --- // --- Reactive Calculations for Virtualization ---
// $: is a Svelte feature that re-runs code when its dependencies change. // $: is a Svelte feature that re-runs code when its dependencies change.
@@ -89,7 +118,7 @@
--col-header-height: {colHeaderHeight}px; --col-header-height: {colHeaderHeight}px;
" "
> >
<!-- The scrollable viewport --> <!-- The scrollable viewport provides the scrollbars -->
<div <div
class="viewport" class="viewport"
bind:this={viewportElement} bind:this={viewportElement}
@@ -100,89 +129,87 @@
scrollLeft = e.currentTarget.scrollLeft; scrollLeft = e.currentTarget.scrollLeft;
}} }}
> >
<!-- Sizer div: creates the full scrollable area --> <!-- Sizer div creates the full scrollable area -->
<div <div
class="total-sizer" class="total-sizer"
style:width="{numCols * colWidth}px" style:width="{numCols * colWidth}px"
style:height="{numRows * rowHeight}px" style:height="{numRows * rowHeight}px"
/> />
</div>
{#if gridData} <!-- The Renderer sits on top of the viewport and handles drawing -->
<!-- Render Window: contains only the visible cells --> {#if gridData}
<div class="renderer">
<!-- Top-left corner -->
<div <div
class="render-window" class="top-left-corner"
style:transform="translate({scrollLeft}px, {scrollTop}px)" class:active-header-corner={activeCell !== null}
> />
<!-- Top-left corner -->
<div
class="top-left-corner"
class:active-header-corner={activeCell !== null}
/>
<!-- Visible Column Headers --> <!-- Visible Column Headers -->
<div <div
class="col-headers-container" class="col-headers-container"
style:transform="translateX(-{scrollLeft % colWidth}px)" style:transform="translateX(-{scrollLeft}px)"
> >
{#each visibleCols as j (j)}
<div
class="header-cell"
style:left="{j * colWidth}px"
class:active-header={activeCell !== null && activeCell[1] === j}
>
{columnLabels[j]}
</div>
{/each}
</div>
<!-- Visible Row Headers -->
<div
class="row-headers-container"
style:transform="translateY(-{scrollTop}px)"
>
{#each visibleRows as i (i)}
<div
class="row-number-cell"
style:top="{i * rowHeight}px"
class:active-header={activeCell !== null && activeCell[0] === i}
>
{i + 1}
</div>
{/each}
</div>
<!-- Visible Grid Cells -->
<div
class="cells-container"
style:transform="translate(-{scrollLeft}px, -{scrollTop}px)"
>
{#each visibleRows as i (i)}
{#each visibleCols as j (j)} {#each visibleCols as j (j)}
<div <div
class="header-cell" class="grid-cell"
style:left="{j * colWidth}px"
class:active-header={activeCell !== null && activeCell[1] === j}
>
{columnLabels[j]}
</div>
{/each}
</div>
<!-- Visible Row Headers -->
<div
class="row-headers-container"
style:transform="translateY(-{scrollTop % rowHeight}px)"
>
{#each visibleRows as i (i)}
<div
class="row-number-cell"
style:top="{i * rowHeight}px" style:top="{i * rowHeight}px"
class:active-header={activeCell !== null && activeCell[0] === i} style:left="{j * colWidth}px"
> >
{i + 1} <!-- Using a standard input to ensure styles are applied correctly -->
<input
type="text"
bind:value={gridData[i][j]}
class="cell-input"
on:focus={() => (activeCell = [i, j])}
on:blur={() => handleCellBlur(i, j)}
/>
<!-- Active cell indicator with fill handle -->
{#if activeCell && activeCell[0] === i && activeCell[1] === j}
<div class="active-cell-indicator">
<div class="fill-handle" />
</div>
{/if}
</div> </div>
{/each} {/each}
</div> {/each}
<!-- Visible Grid Cells -->
<div
class="cells-container"
style:transform="translate(-{scrollLeft % colWidth}px, -{scrollTop %
rowHeight}px)"
>
{#each visibleRows as i (i)}
{#each visibleCols as j (j)}
<div
class="grid-cell"
style:top="{i * rowHeight}px"
style:left="{j * colWidth}px"
>
<Input
type="text"
bind:value={gridData[i][j]}
class="cell-input"
on:focus={() => (activeCell = [i, j])}
/>
<!-- Active cell indicator with fill handle -->
{#if activeCell && activeCell[0] === i && activeCell[1] === j}
<div class="active-cell-indicator">
<div class="fill-handle" />
</div>
{/if}
</div>
{/each}
{/each}
</div>
</div> </div>
{/if} </div>
</div> {/if}
</div> </div>
</div> </div>
@@ -199,20 +226,23 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
position: relative; position: absolute;
top: 0;
left: 0;
} }
.total-sizer { .total-sizer {
position: relative; position: relative;
pointer-events: none; pointer-events: none;
} }
.render-window {
.renderer {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none;
overflow: hidden; overflow: hidden;
pointer-events: none; /* Pass clicks through to elements below */
} }
.top-left-corner, .top-left-corner,
@@ -291,42 +321,51 @@
border-right: 1px solid hsl(var(--border) / 0.7); border-right: 1px solid hsl(var(--border) / 0.7);
border-bottom: 1px solid hsl(var(--border) / 0.7); border-bottom: 1px solid hsl(var(--border) / 0.7);
pointer-events: auto; pointer-events: auto;
background-color: hsl(var(--background));
} }
.cell-input { .cell-input {
/* Overriding all default input styles */
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 0 0.5rem; padding: 0 0.5rem;
margin: 0;
background-color: transparent; background-color: transparent;
border: none;
border-radius: 0; border-radius: 0;
border: 1px solid black;
font-size: 0.875rem; font-size: 0.875rem;
font-family: inherit;
text-align: left; text-align: left;
outline: none; outline: none;
box-shadow: none; box-shadow: none;
} }
.cell-input:focus { /* Ensure no focus styles are added by the browser or libraries */
box-shadow: none; /* Removes shadcn default focus ring */ .cell-input:focus,
.cell-input:focus-visible {
border: 1px solid red;
} }
.active-cell-indicator { .active-cell-indicator {
position: absolute; position: absolute;
top: 0; top: -1px;
left: 0; left: -1px;
width: 100%; width: calc(100% + 2px);
height: 100%; height: calc(100% + 2px);
border: 2px solid hsl(var(--primary)); border: 2px solid hsl(var(--primary));
pointer-events: none; pointer-events: none;
z-index: 5; z-index: 5;
} }
.fill-handle { .fill-handle {
position: absolute; position: absolute;
bottom: -3px; bottom: -4px;
right: -3px; right: -4px;
width: 5px; width: 6px;
height: 5px; height: 6px;
background: hsl(var(--primary)); background: hsl(var(--primary));
border: 1px solid hsl(var(--primary-foreground));
cursor: crosshair; cursor: crosshair;
} }
</style> </style>