improve economic calendar
This commit is contained in:
parent
f9b74774b1
commit
61713839ee
@ -2,9 +2,11 @@
|
|||||||
import { format, startOfWeek, addDays, addWeeks, subWeeks, differenceInWeeks } from 'date-fns';
|
import { format, startOfWeek, addDays, addWeeks, subWeeks, differenceInWeeks } from 'date-fns';
|
||||||
import { screenWidth, numberOfUnreadNotification } from '$lib/store';
|
import { screenWidth, numberOfUnreadNotification } from '$lib/store';
|
||||||
import logo from '$lib/images/transcripts_logo.png';
|
import logo from '$lib/images/transcripts_logo.png';
|
||||||
import { listOfCountries } from '$lib/utils';
|
import { abbreviateNumber, listOfRelevantCountries } from '$lib/utils';
|
||||||
import ArrowLogo from "lucide-svelte/icons/move-up-right";
|
import ArrowLogo from "lucide-svelte/icons/move-up-right";
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js";
|
||||||
|
import { Button } from "$lib/components/shadcn/button/index.js";
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
let rawData;
|
let rawData;
|
||||||
@ -17,6 +19,8 @@
|
|||||||
let currentWeek = startOfWeek(today, { weekStartsOn: 1 });
|
let currentWeek = startOfWeek(today, { weekStartsOn: 1 });
|
||||||
let previousMax = false;
|
let previousMax = false;
|
||||||
let nextMax = false;
|
let nextMax = false;
|
||||||
|
let searchQuery = '';
|
||||||
|
$: testList = [];
|
||||||
|
|
||||||
$: economicCalendar = data?.getEconomicCalendar;
|
$: economicCalendar = data?.getEconomicCalendar;
|
||||||
$: daysOfWeek = getDaysOfWeek(currentWeek);
|
$: daysOfWeek = getDaysOfWeek(currentWeek);
|
||||||
@ -45,17 +49,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFilter(e, newFilter) {
|
|
||||||
const filterSet = new Set(filterList);
|
|
||||||
filterSet.has(newFilter) ? filterSet.delete(newFilter) : filterSet.add(newFilter);
|
|
||||||
filterList = Array.from(filterSet);
|
|
||||||
|
|
||||||
if (filterList.length !== 0) {
|
|
||||||
await loadWorker();
|
|
||||||
} else {
|
|
||||||
weekday = rawData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleMessage = (event) => {
|
const handleMessage = (event) => {
|
||||||
weekdayFiltered = event.data?.finalData?.output ?? [];
|
weekdayFiltered = event.data?.finalData?.output ?? [];
|
||||||
@ -93,6 +86,50 @@
|
|||||||
function changeWeek(state) {
|
function changeWeek(state) {
|
||||||
currentWeek = state === 'previous' ? subWeeks(currentWeek, 1) : addWeeks(currentWeek, 1);
|
currentWeek = state === 'previous' ? subWeeks(currentWeek, 1) : addWeeks(currentWeek, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function handleInput(event) {
|
||||||
|
const searchQuery = event.target.value?.toLowerCase() || '';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
testList = [];
|
||||||
|
|
||||||
|
if (searchQuery.length > 0) {
|
||||||
|
|
||||||
|
const rawList = listOfRelevantCountries;
|
||||||
|
testList = rawList?.filter(item => {
|
||||||
|
const index = item?.toLowerCase();
|
||||||
|
// Check if country starts with searchQuery
|
||||||
|
return index?.startsWith(searchQuery);
|
||||||
|
}) || [];
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$: checkedItems = new Set();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async function handleChangeValue(value) {
|
||||||
|
if (checkedItems.has(value)) {
|
||||||
|
checkedItems.delete(value);
|
||||||
|
} else {
|
||||||
|
checkedItems.add(value);
|
||||||
|
}
|
||||||
|
const filterSet = new Set(filterList);
|
||||||
|
filterSet.has(value) ? filterSet.delete(value) : filterSet.add(value);
|
||||||
|
filterList = Array.from(filterSet);
|
||||||
|
|
||||||
|
if (filterList.length !== 0) {
|
||||||
|
await loadWorker();
|
||||||
|
} else {
|
||||||
|
weekday = rawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@ -226,60 +263,48 @@
|
|||||||
|
|
||||||
<div class="flex flex-row items-center w-fit ml-auto mt-6 mb-2 mr-3 sm:mr-0">
|
<div class="flex flex-row items-center w-fit ml-auto mt-6 mb-2 mr-3 sm:mr-0">
|
||||||
{#if filterList?.length !== 0}
|
{#if filterList?.length !== 0}
|
||||||
<label on:click={() => filterList = [] } class="mr-3 text-sm cursor-pointer bg-[#27272A] sm:hover:bg-[#27272A] text-white duration-100 transition ease-in-out px-4 py-2 rounded-lg shadow-lg ml-auto">
|
<label on:click={() => {filterList = []; checkedItems = new Set();} } class="mr-3 text-sm cursor-pointer bg-[#27272A] sm:hover:bg-[#27272A] text-white duration-100 transition ease-in-out px-4 py-2 rounded-lg shadow-lg ml-auto">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block w-4 h-4" viewBox="0 0 21 21"><g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M3.578 6.487A8 8 0 1 1 2.5 10.5"/><path d="M7.5 6.5h-4v-4"/></g></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block w-4 h-4" viewBox="0 0 21 21"><g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M3.578 6.487A8 8 0 1 1 2.5 10.5"/><path d="M7.5 6.5h-4v-4"/></g></svg>
|
||||||
Reset All
|
Reset All
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="dropdown dropdown-end z-30">
|
<DropdownMenu.Root>
|
||||||
<button tabindex="0" role="button" class="text-sm cursor-pointer text-white bg-[#27272A] sm:hover:bg-[#27272A] duration-100 transition ease-in-out px-4 py-2 rounded-lg shadow-lg ml-auto">
|
<DropdownMenu.Trigger asChild let:builder>
|
||||||
Filter
|
<Button builders={[builder]} class="border-gray-600 border bg-[#09090B] sm:hover:bg-[#27272A] ease-out flex flex-row justify-between items-center px-3 py-2 text-white rounded-lg truncate">
|
||||||
<svg class="inline-block w-2.5 h-2.5 ml-1" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
<span class="truncate text-white">Filter</span>
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
|
<svg class="-mr-1 ml-1 h-5 w-5 xs:ml-2 inline-block" viewBox="0 0 20 20" fill="currentColor" style="max-width:40px" aria-hidden="true">
|
||||||
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</Button>
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
<!-- Dropdown menu -->
|
<DropdownMenu.Content class="w-56 h-fit max-h-72 overflow-y-auto scroller">
|
||||||
|
<div class="relative sticky z-40 focus:outline-none -top-1"
|
||||||
<ul tabindex="0" class="z-30 dropdown-content p-2 shadow bg-[#1D232A] rounded w-60 h-72 oveflow-hidden overflow-y-scroll">
|
tabindex="0" role="menu" style="">
|
||||||
|
<input bind:value={searchQuery}
|
||||||
<div class="mb-3 mt-1 ml-1">
|
on:input={handleInput}
|
||||||
<span class="text-white text-sm mb-2">
|
autocomplete="off"
|
||||||
Popular
|
class=" absolute fixed sticky w-full border-0 bg-[#09090B] border-b border-gray-200
|
||||||
</span>
|
focus:border-gray-200 focus:ring-0 text-white placeholder:text-gray-300"
|
||||||
<hr class="mt-2 mb-2 border-gray-500"/>
|
type="search"
|
||||||
{#each ['United States','Russia','China','UK','EU'] as item}
|
placeholder="Search...">
|
||||||
<li>
|
</div>
|
||||||
<label on:click|stopPropagation={(event) => handleFilter(event, item)} class="flex items-center ps-2 rounded">
|
<DropdownMenu.Group>
|
||||||
<input checked={filterList?.includes(item)} type="checkbox" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 focus:ring-2">
|
{#each (testList.length > 0 && searchQuery?.length > 0 ? testList : searchQuery?.length > 0 && testList?.length === 0 ? [] : listOfRelevantCountries ) as item}
|
||||||
<label class="w-full py-2 ms-2 text-sm font-medium text-white rounded cursor-pointer">
|
<DropdownMenu.Item class="sm:hover:bg-[#27272A]">
|
||||||
{item}
|
<div class="flex items-center" >
|
||||||
|
<label on:click={() => {handleChangeValue(item)}} class="cursor-pointer text-white" for={item}>
|
||||||
|
<input type="checkbox" checked={checkedItems?.has(item)}>
|
||||||
|
<span class="ml-2">{item}</span>
|
||||||
</label>
|
</label>
|
||||||
</label>
|
</div>
|
||||||
</li>
|
</DropdownMenu.Item>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</DropdownMenu.Group>
|
||||||
<div>
|
</DropdownMenu.Content>
|
||||||
<span class="text-white text-sm mb-2 ml-1">
|
</DropdownMenu.Root>
|
||||||
All Countries
|
|
||||||
</span>
|
|
||||||
<hr class="mt-2 mb-2 border-gray-500"/>
|
|
||||||
</div>
|
|
||||||
{#each listOfCountries as item}
|
|
||||||
<li>
|
|
||||||
<div on:click|stopPropagation={(event) => handleFilter(event, item)} class="flex items-center ps-2 rounded">
|
|
||||||
<input type="checkbox" checked={filterList?.includes(item)} class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 focus:ring-2">
|
|
||||||
<label class="w-full py-2 ms-2 text-sm font-medium text-white rounded cursor-pointer">
|
|
||||||
{item}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -294,12 +319,12 @@
|
|||||||
<tr class="whitespace-nowrap">
|
<tr class="whitespace-nowrap">
|
||||||
<th class="text-start text-white font-semibold text-sm"></th>
|
<th class="text-start text-white font-semibold text-sm"></th>
|
||||||
|
|
||||||
<th class="text-start text-white font-semibold text-sm"></th>
|
<th class="text-start text-white font-semibold text-sm sm:text-[1rem]"></th>
|
||||||
<th class="text-start text-white font-semibold text-sm">Event</th>
|
<th class="text-start text-white font-semibold text-sm sm:text-[1rem]">Event</th>
|
||||||
<th class="text-end text-white font-semibold text-sm">Previous</th>
|
<th class="text-end text-white font-semibold text-sm sm:text-[1rem]">Previous</th>
|
||||||
<th class="text-end text-white font-semibold text-sm">Estimated</th>
|
<th class="text-end text-white font-semibold text-sm sm:text-[1rem]">Forecast</th>
|
||||||
<th class="text-end text-white font-semibold text-sm">Actual</th>
|
<th class="text-end text-white font-semibold text-sm sm:text-[1rem]">Actual</th>
|
||||||
<th class="text-white font-semibold text-sm text-end">Impact</th>
|
<th class="text-white font-semibold text-sm sm:text-[1rem] text-end">Importance</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -326,26 +351,42 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<td class="text-start text-white border-b-[#09090B] text-sm sm:text-[1rem] whitespace-nowrap">
|
<td class="text-start text-white border-b-[#09090B] text-sm sm:text-[1rem] whitespace-nowrap">
|
||||||
{item?.event?.length > 15 ? item?.event?.slice(0,15) + '...' : item?.event}
|
{item?.event?.length > 30 ? item?.event?.slice(0,30) + '...' : item?.event}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="text-white border-b-[#09090B] text-end text-sm sm:text-[1rem] whitespace-nowrap">
|
<td class="text-white border-b-[#09090B] text-end text-sm sm:text-[1rem] whitespace-nowrap">
|
||||||
{item?.previous !== null ? item?.previous : '-'}
|
{item?.prior !== (null || '') ? abbreviateNumber(item?.prior) : '-'}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="text-white border-b-[#09090B] text-end text-sm sm:text-[1rem] whitespace-nowrap">
|
<td class="text-white border-b-[#09090B] text-end text-sm sm:text-[1rem] whitespace-nowrap">
|
||||||
{item?.estimate !== null ? item?.estimate : '-'}
|
{item?.consensus !== (null || '') ? abbreviateNumber(item?.consensus) : '-'}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="text-white border-b-[#09090B] text-end text-sm sm:text-[1rem] whitespace-nowrap">
|
<td class="text-white border-b-[#09090B] text-end text-sm sm:text-[1rem] whitespace-nowrap">
|
||||||
{item?.actual !== null ? item?.actual : '-'}
|
{item?.actual !== null ? abbreviateNumber(item?.actual) : '-'}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="{item?.impact === 'Low' ? 'text-[#00FC50]' : item?.impact === 'Medium' ? 'text-[#3DDBFE]' : item?.impact === 'High' ? 'text-[#FC2120]' : 'text-white'} text-end text-sm sm:text-[1rem] whitespace-nowrap border-b-[#09090B]">
|
<td class="text-white text-start text-sm sm:text-[1rem] whitespace-nowrap border-b-[#09090B]">
|
||||||
{item?.impact}
|
<div class="flex flex-row items-center justify-end">
|
||||||
|
{#each Array.from({ length: 3 }) as _, i}
|
||||||
|
{#if i < Math.floor(item?.importance)}
|
||||||
|
<svg class="w-4 h-4 text-[#FFA500]" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 22 20">
|
||||||
|
<path d="M20.924 7.625a1.523 1.523 0 0 0-1.238-1.044l-5.051-.734-2.259-4.577a1.534 1.534 0 0 0-2.752 0L7.365 5.847l-5.051.734A1.535 1.535 0 0 0 1.463 9.2l3.656 3.563-.863 5.031a1.532 1.532 0 0 0 2.226 1.616L11 17.033l4.518 2.375a1.534 1.534 0 0 0 2.226-1.617l-.863-5.03L20.537 9.2a1.523 1.523 0 0 0 .387-1.575Z"/>
|
||||||
|
</svg>
|
||||||
|
{:else}
|
||||||
|
<svg class="w-4 h-4 text-gray-300 dark:text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 22 20">
|
||||||
|
<path d="M20.924 7.625a1.523 1.523 0 0 0-1.238-1.044l-5.051-.734-2.259-4.577a1.534 1.534 0 0 0-2.752 0L7.365 5.847l-5.051.734A1.535 1.535 0 0 0 1.463 9.2l3.656 3.563-.863 5.031a1.532 1.532 0 0 0 2.226 1.616L11 17.033l4.518 2.375a1.534 1.534 0 0 0 2.226-1.617l-.863-5.03L20.537 9.2a1.523 1.523 0 0 0 .387-1.575Z"/>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,29 +1,28 @@
|
|||||||
import { getCache, setCache } from '$lib/store';
|
import { getCache, setCache } from "$lib/store";
|
||||||
|
|
||||||
|
|
||||||
export const load = async ({ parent }) => {
|
export const load = async ({ parent }) => {
|
||||||
const getEconomicCalendar = async () => {
|
const getEconomicCalendar = async () => {
|
||||||
let output;
|
let output;
|
||||||
|
|
||||||
// Get cached data for the specific tickerID
|
// Get cached data for the specific tickerID
|
||||||
const cachedData = getCache('', 'getEconomicCalendar');
|
const cachedData = getCache("", "getEconomicCalendar");
|
||||||
if (cachedData) {
|
if (cachedData) {
|
||||||
output = cachedData;
|
output = cachedData;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
const { apiURL, apiKey } = await parent();
|
const { apiURL, apiKey } = await parent();
|
||||||
// make the POST request to the endpoint
|
// make the POST request to the endpoint
|
||||||
const response = await fetch(apiURL + '/economic-calendar', {
|
const response = await fetch(apiURL + "/economic-calendar", {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json", "X-API-KEY": apiKey
|
"Content-Type": "application/json",
|
||||||
|
"X-API-KEY": apiKey,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
output = await response.json();
|
output = await response.json();
|
||||||
|
|
||||||
// Cache the data for this specific tickerID with a specific name 'getEconomicCalendar'
|
// Cache the data for this specific tickerID with a specific name 'getEconomicCalendar'
|
||||||
setCache('', output, 'getEconomicCalendar');
|
setCache("", output, "getEconomicCalendar");
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
@ -31,6 +30,6 @@ export const load = async ({parent}) => {
|
|||||||
|
|
||||||
// Make sure to return a promise
|
// Make sure to return a promise
|
||||||
return {
|
return {
|
||||||
getEconomicCalendar: await getEconomicCalendar()
|
getEconomicCalendar: await getEconomicCalendar(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -1,17 +1,66 @@
|
|||||||
|
export const listOfRelevantCountries = [
|
||||||
|
{ USA: "United States" },
|
||||||
|
{ CHN: "China" },
|
||||||
|
{ CAN: "Canada" },
|
||||||
|
{ GBR: "United Kingdom" },
|
||||||
|
{ JPN: "Japan" },
|
||||||
|
{ ISR: "Israel" },
|
||||||
|
{ BRA: "Brazil" },
|
||||||
|
{ FRA: "France" },
|
||||||
|
{ IRL: "Ireland" },
|
||||||
|
{ DEU: "Germany" },
|
||||||
|
{ MEX: "Mexico" },
|
||||||
|
{ IND: "India" },
|
||||||
|
{ AUS: "Australia" },
|
||||||
|
{ KOR: "South Korea" },
|
||||||
|
{ SWE: "Sweden" },
|
||||||
|
{ NLD: "Netherlands" },
|
||||||
|
{ CHE: "Switzerland" },
|
||||||
|
{ TWN: "Taiwan" },
|
||||||
|
{ ZAF: "South Africa" },
|
||||||
|
{ HKG: "Hong Kong" },
|
||||||
|
{ SGP: "Singapore" },
|
||||||
|
{ ARG: "Argentina" },
|
||||||
|
{ CHL: "Chile" },
|
||||||
|
{ PHL: "Philippines" },
|
||||||
|
{ TUR: "Turkey" },
|
||||||
|
{ ITA: "Italy" },
|
||||||
|
{ IDN: "Indonesia" },
|
||||||
|
{ MYS: "Malaysia" },
|
||||||
|
{ LUX: "Luxembourg" },
|
||||||
|
{ VNM: "Vietnam" },
|
||||||
|
{ NZL: "New Zealand" },
|
||||||
|
{ DNK: "Denmark" },
|
||||||
|
{ NOR: "Norway" },
|
||||||
|
{ FIN: "Finland" },
|
||||||
|
{ RUS: "Russia" },
|
||||||
|
{ ARE: "United Arab Emirates" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const countryMap = Object.fromEntries(
|
||||||
|
listOfRelevantCountries.map((entry) => {
|
||||||
|
const [code, name] = Object.entries(entry)[0];
|
||||||
|
return [name, code];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
onmessage = async (event: MessageEvent) => {
|
onmessage = async (event: MessageEvent) => {
|
||||||
const rawData = event.data?.rawData;
|
const rawData = event.data?.rawData;
|
||||||
const filterList = event.data?.filterList;
|
const filterList = event.data?.filterList;
|
||||||
const output = rawData?.map(subArray =>
|
|
||||||
subArray?.filter(item => filterList?.includes(item?.country))
|
// Map filterList country names to abbreviations
|
||||||
|
const filterCodes = filterList?.map((name) => countryMap[name]) || [];
|
||||||
|
|
||||||
|
// Filter rawData based on the mapped country codes
|
||||||
|
const output = rawData?.map((subArray) =>
|
||||||
|
subArray?.filter((item) => filterCodes.includes(item?.country))
|
||||||
);
|
);
|
||||||
|
|
||||||
let finalData = { output };
|
let finalData = { output };
|
||||||
postMessage({ message: 'success', finalData});
|
postMessage({ message: "success", finalData });
|
||||||
|
|
||||||
// Sending data back to the main thread
|
// Sending data back to the main thread
|
||||||
//postMessage({ message: 'Data received in the worker', ticker, apiURL });
|
//postMessage({ message: 'Data received in the worker', ticker, apiURL });
|
||||||
};
|
};
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
||||||
@ -549,7 +549,6 @@ async function handleChangeValue(value) {
|
|||||||
console.warn(`Unhandled rule: ${ruleName}`);
|
console.warn(`Unhandled rule: ${ruleName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(valueMappings[ruleName])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user