add websocket to Table component
This commit is contained in:
parent
1ba7927711
commit
d2bddf9c3c
@ -1,7 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { screenWidth } from "$lib/store";
|
||||
import { abbreviateNumber } from "$lib/utils";
|
||||
import { onMount } from "svelte";
|
||||
import { screenWidth, isOpen } from "$lib/store";
|
||||
import {
|
||||
abbreviateNumber,
|
||||
calculateChange,
|
||||
updateStockList,
|
||||
} from "$lib/utils";
|
||||
import { onMount, afterUpdate, onDestroy } from "svelte";
|
||||
import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js";
|
||||
import { Button } from "$lib/components/shadcn/button/index.js";
|
||||
import HoverStockChart from "$lib/components/HoverStockChart.svelte";
|
||||
@ -20,7 +24,6 @@
|
||||
"eps",
|
||||
"marketCap",
|
||||
]);
|
||||
|
||||
export let specificRows = [];
|
||||
|
||||
export let defaultList = [
|
||||
@ -31,9 +34,10 @@
|
||||
];
|
||||
|
||||
export let hideLastRow = false;
|
||||
|
||||
let originalData = [...rawData]; // Unaltered copy of raw data
|
||||
let ruleOfList = defaultList;
|
||||
let socket;
|
||||
|
||||
const defaultRules = defaultList?.map((item) => item?.rule);
|
||||
|
||||
let pagePathName = $page?.url?.pathname;
|
||||
@ -318,9 +322,63 @@
|
||||
stockList = [...stockList, ...filteredNewResults];
|
||||
}
|
||||
}
|
||||
|
||||
function sendMessage(message) {
|
||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||
socket.send(JSON?.stringify(message));
|
||||
} else {
|
||||
console.error("WebSocket is not open. Unable to send message.");
|
||||
}
|
||||
}
|
||||
|
||||
async function websocketRealtimeData() {
|
||||
try {
|
||||
socket = new WebSocket(data?.wsURL + "/multiple-realtime-data");
|
||||
|
||||
socket.addEventListener("open", () => {
|
||||
console.log("WebSocket connection opened");
|
||||
// Send only current watchlist symbols
|
||||
const tickerList = rawData?.map((item) => item?.symbol) || [];
|
||||
sendMessage(tickerList);
|
||||
});
|
||||
|
||||
socket.addEventListener("message", (event) => {
|
||||
const data = event.data;
|
||||
try {
|
||||
const newList = JSON?.parse(data);
|
||||
if (newList?.length > 0) {
|
||||
//console.log("Received message:", newList);
|
||||
if (originalData.some((item) => "changesPercentage" in item)) {
|
||||
originalData = calculateChange(originalData, newList);
|
||||
stockList = updateStockList(stockList, originalData);
|
||||
setTimeout(() => {
|
||||
stockList = stockList?.map((item) => ({
|
||||
...item,
|
||||
previous: null,
|
||||
}));
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error parsing WebSocket message:", e);
|
||||
}
|
||||
});
|
||||
|
||||
socket.addEventListener("close", (event) => {
|
||||
console.log("WebSocket connection closed:", event.reason);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("WebSocket connection error:", error);
|
||||
}
|
||||
}
|
||||
$: stockList = [...stockList];
|
||||
|
||||
onMount(async () => {
|
||||
// Initialize the download worker if not already done
|
||||
|
||||
if ($isOpen) {
|
||||
await websocketRealtimeData();
|
||||
console.log("WebSocket restarted due to watchlist changes");
|
||||
}
|
||||
try {
|
||||
const savedRules = localStorage?.getItem(pagePathName);
|
||||
|
||||
@ -372,6 +430,59 @@
|
||||
}
|
||||
});
|
||||
|
||||
let previousList = [];
|
||||
let reconnectionTimeout;
|
||||
afterUpdate(async () => {
|
||||
// Compare only the symbols to detect changes
|
||||
const currentSymbols = rawData?.map((item) => item?.symbol).sort();
|
||||
const previousSymbols = previousList?.map((item) => item?.symbol).sort();
|
||||
|
||||
// Check if symbols have changed
|
||||
if (
|
||||
JSON.stringify(currentSymbols) !== JSON.stringify(previousSymbols) &&
|
||||
typeof socket !== "undefined"
|
||||
) {
|
||||
// Update previous list
|
||||
previousList = rawData;
|
||||
|
||||
try {
|
||||
// Close existing socket if open
|
||||
if (socket && socket.readyState !== WebSocket.CLOSED) {
|
||||
socket?.close();
|
||||
}
|
||||
|
||||
// Wait for socket to close
|
||||
await new Promise((resolve) => {
|
||||
socket?.addEventListener("close", resolve, { once: true });
|
||||
});
|
||||
|
||||
// Reconnect with new symbols
|
||||
if ($isOpen) {
|
||||
await websocketRealtimeData();
|
||||
console.log("WebSocket restarted due to watchlist changes");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error restarting WebSocket:", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
try {
|
||||
// Clear any pending reconnection timeout
|
||||
if (reconnectionTimeout) {
|
||||
clearTimeout(reconnectionTimeout);
|
||||
}
|
||||
|
||||
// Close the WebSocket connection
|
||||
if (socket) {
|
||||
socket.close(1000, "Page unloaded");
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
// Function to generate columns based on keys in rawData
|
||||
function generateColumns(data) {
|
||||
const leftAlignKeys = new Set(["rank", "symbol", "name"]);
|
||||
@ -723,7 +834,27 @@
|
||||
{:else if column?.type === "decimal"}
|
||||
{item[column.key]?.toLocaleString("en-US")}
|
||||
{:else if column.key === "price"}
|
||||
{item[column.key]?.toFixed(2)}
|
||||
<div class="relative flex items-center justify-end">
|
||||
{#if item?.previous !== null && item?.previous !== undefined && item?.previous !== item[column?.key]}
|
||||
<span
|
||||
class="absolute h-1 w-1 {item[column?.key] < 10
|
||||
? 'right-[35px] sm:right-[40px]'
|
||||
: item[column?.key] < 100
|
||||
? 'right-[40px] sm:right-[45px]'
|
||||
: 'right-[45px] sm:right-[55px]'} bottom-0 -top-0.5 sm:-top-1"
|
||||
>
|
||||
<span
|
||||
class="inline-flex rounded-full h-1 w-1 {item?.previous >
|
||||
item[column?.key]
|
||||
? 'bg-[#FF2F1F]'
|
||||
: 'bg-[#00FC50]'} pulse-animation"
|
||||
></span>
|
||||
</span>
|
||||
{/if}
|
||||
{item[column.key] !== null
|
||||
? item[column.key]?.toFixed(2)
|
||||
: "-"}
|
||||
</div>
|
||||
{:else if column.type === "percent"}
|
||||
{item[column.key]?.toFixed(2) + "%"}
|
||||
{:else if column.type === "percentSign"}
|
||||
@ -785,3 +916,25 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
70% {
|
||||
transform: scale(1.1); /* Adjust scale as needed for pulse effect */
|
||||
opacity: 0.8;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1); /* End scale */
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply the animation styles to the element */
|
||||
.pulse-animation {
|
||||
animation: pulse 500ms ease-out forwards; /* 300ms duration */
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -18,6 +18,58 @@ type FlyAndScaleParams = {
|
||||
duration?: number;
|
||||
};
|
||||
|
||||
export const calculateChange = (oldList?: any[], newList?: any[]) => {
|
||||
if (!oldList?.length || !newList?.length) return [...(oldList || [])];
|
||||
|
||||
const newListMap = new Map(newList.map(item => [item.symbol, item]));
|
||||
|
||||
for (let i = 0, len = oldList.length; i < len; i++) {
|
||||
const item = oldList[i];
|
||||
const newItem = newListMap.get(item.symbol);
|
||||
|
||||
if (newItem?.ap) {
|
||||
const { price, changesPercentage } = item;
|
||||
const newPrice = newItem.ap;
|
||||
|
||||
if (price != null && changesPercentage != null) {
|
||||
const baseLine = price / (1 + Number(changesPercentage) / 100);
|
||||
item.changesPercentage = ((newPrice / baseLine - 1) * 100);
|
||||
}
|
||||
|
||||
item.previous = price;
|
||||
item.price = newPrice;
|
||||
}
|
||||
}
|
||||
|
||||
return oldList;
|
||||
};
|
||||
|
||||
export function updateStockList(stockList, originalData) {
|
||||
// Create a Map for O(1) lookup of original data by symbol
|
||||
const originalDataMap = new Map(
|
||||
originalData?.map(item => [item.symbol, item])
|
||||
);
|
||||
|
||||
// Use .map() to create a new array with updated stocks
|
||||
return stockList?.map(stock => {
|
||||
// Find matching stock in originalData
|
||||
const matchingStock = originalDataMap?.get(stock?.symbol);
|
||||
|
||||
// If a match is found, update price and changesPercentage
|
||||
if (matchingStock) {
|
||||
return {
|
||||
...stock,
|
||||
price: matchingStock?.price,
|
||||
changesPercentage: matchingStock?.changesPercentage,
|
||||
previous: matchingStock?.previous ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
// If no match, return the original stock object unchanged
|
||||
return stock;
|
||||
});
|
||||
}
|
||||
|
||||
export const flyAndScale = (
|
||||
node: Element,
|
||||
params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 0 },
|
||||
|
||||
@ -29,6 +29,10 @@
|
||||
loginData,
|
||||
numberOfUnreadNotification,
|
||||
clientSideCache,
|
||||
isOpen,
|
||||
isAfterMarketClose,
|
||||
isBeforeMarketOpen,
|
||||
isWeekend,
|
||||
} from "$lib/store";
|
||||
|
||||
import { Button } from "$lib/components/shadcn/button/index.ts";
|
||||
@ -127,6 +131,7 @@
|
||||
|
||||
onMount(async () => {
|
||||
//await fallbackWorker();
|
||||
await checkMarketHour();
|
||||
await loadWorker();
|
||||
//await pushNotification()
|
||||
|
||||
@ -175,6 +180,50 @@
|
||||
$clientSideCache[$etfTicker] = {};
|
||||
}
|
||||
}
|
||||
|
||||
const checkMarketHour = async () => {
|
||||
const holidays = [
|
||||
"2024-01-01",
|
||||
"2024-01-15",
|
||||
"2024-02-19",
|
||||
"2024-03-29",
|
||||
"2024-05-27",
|
||||
"2024-06-19",
|
||||
"2024-07-04",
|
||||
"2024-09-02",
|
||||
"2024-11-28",
|
||||
"2024-12-25",
|
||||
];
|
||||
const currentDate = new Date().toISOString().split("T")[0];
|
||||
|
||||
// Get the current time in the ET time zone
|
||||
const etTimeZone = "America/New_York";
|
||||
const currentTime = new Date().toLocaleString("en-US", {
|
||||
timeZone: etTimeZone,
|
||||
});
|
||||
|
||||
// Determine if the NYSE is currently open or closed
|
||||
const currentHour = new Date(currentTime).getHours();
|
||||
const isWeekendValue =
|
||||
new Date(currentTime).getDay() === 6 ||
|
||||
new Date(currentTime).getDay() === 0;
|
||||
const isBeforeMarketOpenValue =
|
||||
currentHour < 9 ||
|
||||
(currentHour === 9 && new Date(currentTime).getMinutes() < 30);
|
||||
const isAfterMarketCloseValue = currentHour >= 16;
|
||||
|
||||
isOpen.set(
|
||||
!(
|
||||
isWeekendValue ||
|
||||
isBeforeMarketOpenValue ||
|
||||
isAfterMarketCloseValue ||
|
||||
holidays?.includes(currentDate)
|
||||
),
|
||||
);
|
||||
isWeekend.set(isWeekendValue);
|
||||
isBeforeMarketOpen.set(isBeforeMarketOpenValue);
|
||||
isAfterMarketClose.set(isAfterMarketCloseValue);
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth={$screenWidth} />
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
import {
|
||||
isOpen,
|
||||
isAfterMarketClose,
|
||||
isBeforeMarketOpen,
|
||||
isWeekend,
|
||||
} from "$lib/store";
|
||||
|
||||
const checkMarketHour = async () => {
|
||||
const holidays = [
|
||||
"2024-01-01",
|
||||
"2024-01-15",
|
||||
"2024-02-19",
|
||||
"2024-03-29",
|
||||
"2024-05-27",
|
||||
"2024-06-19",
|
||||
"2024-07-04",
|
||||
"2024-09-02",
|
||||
"2024-11-28",
|
||||
"2024-12-25",
|
||||
];
|
||||
const currentDate = new Date().toISOString().split("T")[0];
|
||||
|
||||
// Get the current time in the ET time zone
|
||||
const etTimeZone = "America/New_York";
|
||||
const currentTime = new Date().toLocaleString("en-US", {
|
||||
timeZone: etTimeZone,
|
||||
});
|
||||
|
||||
// Determine if the NYSE is currently open or closed
|
||||
const currentHour = new Date(currentTime).getHours();
|
||||
const isWeekendValue =
|
||||
new Date(currentTime).getDay() === 6 ||
|
||||
new Date(currentTime).getDay() === 0;
|
||||
const isBeforeMarketOpenValue =
|
||||
currentHour < 9 ||
|
||||
(currentHour === 9 && new Date(currentTime).getMinutes() < 30);
|
||||
const isAfterMarketCloseValue = currentHour >= 16;
|
||||
|
||||
isOpen.set(
|
||||
!(
|
||||
isWeekendValue ||
|
||||
isBeforeMarketOpenValue ||
|
||||
isAfterMarketCloseValue ||
|
||||
holidays?.includes(currentDate)
|
||||
),
|
||||
);
|
||||
isWeekend.set(isWeekendValue);
|
||||
isBeforeMarketOpen.set(isBeforeMarketOpenValue);
|
||||
isAfterMarketClose.set(isAfterMarketCloseValue);
|
||||
};
|
||||
|
||||
export const load = async ({ params, data }) => {
|
||||
await checkMarketHour();
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { screenWidth, numberOfUnreadNotification, isOpen } from "$lib/store";
|
||||
import { formatDate, abbreviateNumber } from "$lib/utils";
|
||||
import { formatDate, abbreviateNumber, calculateChange } from "$lib/utils";
|
||||
import toast from "svelte-french-toast";
|
||||
import { onMount, onDestroy, afterUpdate } from "svelte";
|
||||
import Input from "$lib/components/Input.svelte";
|
||||
@ -136,31 +136,6 @@
|
||||
let displayWatchList;
|
||||
let allList = data?.getAllWatchlist;
|
||||
|
||||
function calculateChange(oldList, newList) {
|
||||
// Create a map for faster lookups
|
||||
const newListMap = new Map(newList.map((item) => [item.symbol, item]));
|
||||
|
||||
// Use for loop instead of forEach for better performance
|
||||
for (let i = 0; i < oldList?.length; i++) {
|
||||
const item = oldList[i];
|
||||
const newItem = newListMap?.get(item?.symbol);
|
||||
|
||||
if (newItem) {
|
||||
// Calculate the new changePercentage
|
||||
const baseLine = item?.price / (1 + item?.changesPercentage / 100);
|
||||
const newPrice = newItem?.ap;
|
||||
const newChangePercentage = (newPrice / baseLine - 1) * 100;
|
||||
|
||||
// Update the item directly in the oldList
|
||||
item.previous = item.price;
|
||||
item.price = newPrice;
|
||||
item.changesPercentage = newChangePercentage;
|
||||
}
|
||||
}
|
||||
|
||||
return [...oldList];
|
||||
}
|
||||
|
||||
const handleDownloadMessage = (event) => {
|
||||
isLoaded = false;
|
||||
watchList = event?.data?.watchlistData ?? [];
|
||||
@ -1281,7 +1256,13 @@
|
||||
>
|
||||
{#if item?.previous !== null && item?.previous !== undefined && item?.previous !== item[row?.rule] && row?.rule === "price"}
|
||||
<span
|
||||
class="absolute h-1 w-1 right-12 sm:right-14 bottom-0 -top-1"
|
||||
class="absolute h-1 w-1 {item[
|
||||
row?.rule
|
||||
] < 10
|
||||
? 'right-[35px] sm:right-[40px]'
|
||||
: item[row?.rule] < 100
|
||||
? 'right-[40px] sm:right-[45px]'
|
||||
: 'right-[45px] sm:right-[55px]'} bottom-0 -top-0.5 sm:-top-1"
|
||||
>
|
||||
<span
|
||||
class="inline-flex rounded-full h-1 w-1 {item?.previous >
|
||||
@ -1291,6 +1272,7 @@
|
||||
></span>
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{item[row?.rule] !== null
|
||||
? item[row?.rule]?.toFixed(2)
|
||||
: "-"}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user