Add wish banner detail
|
@ -28,6 +28,7 @@
|
||||||
"@rollup/plugin-replace": "^2.3.4",
|
"@rollup/plugin-replace": "^2.3.4",
|
||||||
"@rollup/plugin-url": "^5.0.0",
|
"@rollup/plugin-url": "^5.0.0",
|
||||||
"autoprefixer": "^10.0.1",
|
"autoprefixer": "^10.0.1",
|
||||||
|
"chart.js": "^2.9.4",
|
||||||
"dayjs": "^1.9.4",
|
"dayjs": "^1.9.4",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"postcss": "^8.1.2",
|
"postcss": "^8.1.2",
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center lg:hidden fixed w-full h-16 header bg-background z-30 shadow-md overflow-hidden">
|
<div class="flex items-center lg:hidden fixed w-full h-16 header bg-background z-50 shadow-md overflow-hidden">
|
||||||
<a href="/" class="flex-1 pl-4 md:pl-8 font-display text-3xl font-black text-white relative z-10 pt-2">
|
<a href="/" class="flex-1 pl-4 md:pl-8 font-display text-3xl font-black text-white relative z-10 pt-2">
|
||||||
Paimon<span class="text-xl text-primary">.moe</span>
|
Paimon<span class="text-xl text-primary">.moe</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -4,13 +4,21 @@
|
||||||
|
|
||||||
import Icon from '../Icon.svelte';
|
import Icon from '../Icon.svelte';
|
||||||
|
|
||||||
|
export let className = '';
|
||||||
|
export let styleList = '';
|
||||||
export let sort = false;
|
export let sort = false;
|
||||||
export let order = false;
|
export let order = false;
|
||||||
export let align = 'left';
|
export let align = 'left';
|
||||||
|
export let padding = 'px-4';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<th class={`text-gray-400 select-none font-display text-lg cursor-pointer px-4 text-${align}`} on:click>
|
<th
|
||||||
<span class="relative"><slot />
|
class={`text-gray-400 select-none font-display text-lg cursor-pointer ${padding} text-${align} ${className}`}
|
||||||
|
style={styleList}
|
||||||
|
on:click
|
||||||
|
>
|
||||||
|
<span class="relative"
|
||||||
|
><slot />
|
||||||
{#if sort}
|
{#if sort}
|
||||||
<div transition:fade={{ duration: 100 }} class="absolute" style="right: -21px; top: 3px;">
|
<div transition:fade={{ duration: 100 }} class="absolute" style="right: -21px; top: 3px;">
|
||||||
<Icon className={`mb-1 duration-100 ${order ? 'transform -rotate-180' : ''}`} path={mdiChevronDown} />
|
<Icon className={`mb-1 duration-100 ${order ? 'transform -rotate-180' : ''}`} path={mdiChevronDown} />
|
||||||
|
|
153
src/data/banners.js
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
export const banners = {
|
||||||
|
beginners: [
|
||||||
|
{
|
||||||
|
name: "Beginners' Wish",
|
||||||
|
start: '2000-01-01 00:00:00',
|
||||||
|
end: '2200-01-01 00:00:00',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
standard: [
|
||||||
|
{
|
||||||
|
name: 'Wanderlust Invocation',
|
||||||
|
start: '2000-01-01 00:00:00',
|
||||||
|
end: '2200-01-01 00:00:00',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
characters: [
|
||||||
|
{
|
||||||
|
name: 'Ballad in Goblets',
|
||||||
|
shortName: 'Venti',
|
||||||
|
start: '2020-09-28 00:00:00',
|
||||||
|
end: '2020-10-18 18:00:00',
|
||||||
|
color: '#55E4B0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sparkling Steps',
|
||||||
|
shortName: 'Klee',
|
||||||
|
start: '2020-10-20 18:00:00',
|
||||||
|
end: '2020-11-10 18:00:00',
|
||||||
|
color: '#CA360E',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Farewell of Snezhnaya',
|
||||||
|
shortName: 'Tartaglia',
|
||||||
|
start: '2020-11-11 06:00:00',
|
||||||
|
end: '2020-12-01 18:00:00',
|
||||||
|
color: '#50A3C0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Gentry of Hermitage',
|
||||||
|
shortName: 'Zhongli',
|
||||||
|
start: '2020-12-01 18:00:00',
|
||||||
|
end: '2020-12-22 15:00:00',
|
||||||
|
color: '#D1A55C',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Secretum Secretorum',
|
||||||
|
shortName: 'Albedo',
|
||||||
|
start: '2020-12-23 10:00:00',
|
||||||
|
end: '2021-01-12 16:00:00',
|
||||||
|
color: '#FCFE83',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Adrift in the Harbor',
|
||||||
|
shortName: 'Ganyu',
|
||||||
|
start: '2021-01-12 18:00:00',
|
||||||
|
end: '2021-02-02 15:00:00',
|
||||||
|
color: '#6994DF',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Invitation to Mundane Life',
|
||||||
|
shortName: 'Xiao',
|
||||||
|
start: '2021-02-03 06:00:00',
|
||||||
|
end: '2021-02-17 16:00:00',
|
||||||
|
color: '#2BE3F8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Dance of Lanterns',
|
||||||
|
shortName: 'Keqing',
|
||||||
|
start: '2021-02-17 18:00:00',
|
||||||
|
end: '2021-03-02 16:00:00',
|
||||||
|
color: '#AB6CD7',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Moment of Bloom',
|
||||||
|
shortName: 'Hu Tao',
|
||||||
|
start: '2021-03-02 18:00:00',
|
||||||
|
end: '2021-03-16 15:00:00',
|
||||||
|
color: '#BF5042',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Ballad in Goblets',
|
||||||
|
shortName: 'Venti',
|
||||||
|
start: '2021-03-17 06:00:00',
|
||||||
|
end: '2021-04-06 16:00:00',
|
||||||
|
color: '#35C297',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
weapons: [
|
||||||
|
{
|
||||||
|
name: 'Epitome Invocation',
|
||||||
|
start: '2020-09-28 00:00:00',
|
||||||
|
end: '2020-10-18 18:00:00',
|
||||||
|
shortName: 'Amos',
|
||||||
|
color: '#f54e42'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Epitome Invocation',
|
||||||
|
start: '2020-10-20 18:00:00',
|
||||||
|
end: '2020-11-10 18:00:00',
|
||||||
|
shortName: 'WGS',
|
||||||
|
color: '#f5c242'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Epitome Invocation',
|
||||||
|
start: '2020-11-11 06:00:00',
|
||||||
|
end: '2020-12-01 18:00:00',
|
||||||
|
shortName: 'Skyward',
|
||||||
|
color: '#f5ef42'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Epitome Invocation',
|
||||||
|
start: '2020-12-01 18:00:00',
|
||||||
|
end: '2020-12-22 15:00:00',
|
||||||
|
shortName: 'Vortex',
|
||||||
|
color: '#7ef542'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Epitome Invocation',
|
||||||
|
start: '2020-12-23 10:00:00',
|
||||||
|
end: '2021-01-12 16:00:00',
|
||||||
|
shortName: 'Summit',
|
||||||
|
color: '#42ecf5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Epitome Invocation',
|
||||||
|
start: '2021-01-12 18:00:00',
|
||||||
|
end: '2021-02-02 15:00:00',
|
||||||
|
shortName: 'Amos',
|
||||||
|
color: '#424ef5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Epitome Invocation',
|
||||||
|
start: '2021-02-03 06:00:00',
|
||||||
|
end: '2021-02-23 16:00:00',
|
||||||
|
shortName: 'Primordial',
|
||||||
|
color: '#b042f5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Epitome Invocation',
|
||||||
|
start: '2021-02-23 18:00:00',
|
||||||
|
end: '2021-03-16 15:00:00',
|
||||||
|
shortName: 'Homa',
|
||||||
|
color: '#f542c8'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Epitome Invocation',
|
||||||
|
start: '2021-03-17 11:00:00',
|
||||||
|
end: '2021-04-06 16:00:00',
|
||||||
|
shortName: 'Elegy',
|
||||||
|
color: '#f54e42'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
|
@ -1071,7 +1071,7 @@ export const weaponList = {
|
||||||
atk: 42,
|
atk: 42,
|
||||||
secondary: 'Elemental Mastery',
|
secondary: 'Elemental Mastery',
|
||||||
type: weapons.sword,
|
type: weapons.sword,
|
||||||
source: 'forgingnorthlander sword prototype x1crystal chunk x50white iron chunk x50',
|
source: 'forging',
|
||||||
ascension: [
|
ascension: [
|
||||||
{
|
{
|
||||||
items: [
|
items: [
|
||||||
|
@ -1130,7 +1130,7 @@ export const weaponList = {
|
||||||
atk: 44,
|
atk: 44,
|
||||||
secondary: 'Physical DMG Bonus',
|
secondary: 'Physical DMG Bonus',
|
||||||
type: weapons.sword,
|
type: weapons.sword,
|
||||||
source: 'adventure rank 10 rewardforging:northlander sword prototype x1crystal chunk x50white iron chunk x50',
|
source: 'adventure rank 10 reward, forging',
|
||||||
ascension: [
|
ascension: [
|
||||||
{
|
{
|
||||||
items: [
|
items: [
|
||||||
|
@ -1307,7 +1307,7 @@ export const weaponList = {
|
||||||
atk: 42,
|
atk: 42,
|
||||||
secondary: 'ATK',
|
secondary: 'ATK',
|
||||||
type: weapons.bow,
|
type: weapons.bow,
|
||||||
source: 'forgingnorthlander bow prototype x1crystal chunk x50white iron chunk x50',
|
source: 'forging',
|
||||||
ascension: [
|
ascension: [
|
||||||
{
|
{
|
||||||
items: [
|
items: [
|
||||||
|
@ -1602,7 +1602,7 @@ export const weaponList = {
|
||||||
atk: 44,
|
atk: 44,
|
||||||
secondary: 'Physical DMG Bonus',
|
secondary: 'Physical DMG Bonus',
|
||||||
type: weapons.polearm,
|
type: weapons.polearm,
|
||||||
source: 'forgingnorthlander polearm prototype x1crystal chunk x50white iron chunk x50',
|
source: 'forging',
|
||||||
ascension: [
|
ascension: [
|
||||||
{
|
{
|
||||||
items: [
|
items: [
|
||||||
|
@ -2664,7 +2664,7 @@ export const weaponList = {
|
||||||
atk: 42,
|
atk: 42,
|
||||||
secondary: 'Energy Recharge',
|
secondary: 'Energy Recharge',
|
||||||
type: weapons.polearm,
|
type: weapons.polearm,
|
||||||
source: 'forgingnorthlander polearm prototype x1crystal chunk x50white iron chunk x50',
|
source: 'forging',
|
||||||
ascension: [
|
ascension: [
|
||||||
{
|
{
|
||||||
items: [
|
items: [
|
||||||
|
@ -2723,7 +2723,7 @@ export const weaponList = {
|
||||||
atk: 42,
|
atk: 42,
|
||||||
secondary: 'DEF',
|
secondary: 'DEF',
|
||||||
type: weapons.claymore,
|
type: weapons.claymore,
|
||||||
source: 'forgingnorthlander claymore prototype x1crystal chunk x50white iron chunk x50',
|
source: 'forging',
|
||||||
ascension: [
|
ascension: [
|
||||||
{
|
{
|
||||||
items: [
|
items: [
|
||||||
|
@ -2841,7 +2841,7 @@ export const weaponList = {
|
||||||
atk: 41,
|
atk: 41,
|
||||||
secondary: 'Physical DMG Bonus',
|
secondary: 'Physical DMG Bonus',
|
||||||
type: weapons.bow,
|
type: weapons.bow,
|
||||||
source: 'forgingnorthlander bow prototype x1crystal chunk x50white iron chunk x50',
|
source: 'forging',
|
||||||
ascension: [
|
ascension: [
|
||||||
{
|
{
|
||||||
items: [
|
items: [
|
||||||
|
@ -3313,7 +3313,7 @@ export const weaponList = {
|
||||||
atk: 44,
|
atk: 44,
|
||||||
secondary: 'Elemental Mastery',
|
secondary: 'Elemental Mastery',
|
||||||
type: weapons.catalyst,
|
type: weapons.catalyst,
|
||||||
source: 'forgingnorthlander catalyst prototype x1crystal chunk x50white iron chunk x50',
|
source: 'forging',
|
||||||
ascension: [
|
ascension: [
|
||||||
{
|
{
|
||||||
items: [
|
items: [
|
||||||
|
@ -3903,7 +3903,7 @@ export const weaponList = {
|
||||||
atk: 44,
|
atk: 44,
|
||||||
secondary: 'ATK',
|
secondary: 'ATK',
|
||||||
type: weapons.claymore,
|
type: weapons.claymore,
|
||||||
source: 'forgingnorthlander claymore prototype x1crystal chunk x50white iron chunk x50',
|
source: 'forging',
|
||||||
ascension: [
|
ascension: [
|
||||||
{
|
{
|
||||||
items: [
|
items: [
|
||||||
|
|
|
@ -127,6 +127,24 @@
|
||||||
"Or you can add or edit the table manually."
|
"Or you can add or edit the table manually."
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"types": {
|
||||||
|
"beginners": "Beginners' Wish",
|
||||||
|
"standard": "Standard",
|
||||||
|
"character-event": "Character Event",
|
||||||
|
"weapon-event": "Weapon Event"
|
||||||
|
},
|
||||||
|
"detail": {
|
||||||
|
"weapon": "Weapon",
|
||||||
|
"character": "Character",
|
||||||
|
"time": "Time",
|
||||||
|
"pity": "Pity",
|
||||||
|
"name": "Name",
|
||||||
|
"type": "Type",
|
||||||
|
"banner": "Banner",
|
||||||
|
"roll": "#Roll",
|
||||||
|
"totalThisBanner": "Total pull on this banner",
|
||||||
|
"worth": "Worth"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"calculator": {
|
"calculator": {
|
||||||
|
|
|
@ -127,6 +127,24 @@
|
||||||
"Atau kamu bisa menambahkan atau mengedit tabel nya secara manual."
|
"Atau kamu bisa menambahkan atau mengedit tabel nya secara manual."
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"types": {
|
||||||
|
"beginners": "Beginners' Wish",
|
||||||
|
"standard": "Standard",
|
||||||
|
"character-event": "Event Karakter",
|
||||||
|
"weapon-event": "Event Senjata"
|
||||||
|
},
|
||||||
|
"detail": {
|
||||||
|
"weapon": "Senjata",
|
||||||
|
"character": "Karakter",
|
||||||
|
"time": "Waktu",
|
||||||
|
"pity": "Pity",
|
||||||
|
"name": "Nama",
|
||||||
|
"type": "Tipe",
|
||||||
|
"banner": "Banner",
|
||||||
|
"roll": "#Roll",
|
||||||
|
"totalThisBanner": "Total pull di banner ini",
|
||||||
|
"worth": "Setara dengan"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"calculator": {
|
"calculator": {
|
||||||
|
|
654
src/routes/wish/[id].svelte
Normal file
|
@ -0,0 +1,654 @@
|
||||||
|
<script context="module">
|
||||||
|
export async function preload(page) {
|
||||||
|
const { id } = page.params;
|
||||||
|
return { id };
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
import { getContext, onMount, tick } from 'svelte';
|
||||||
|
import { mdiArrowLeft, mdiStar } from '@mdi/js';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import Chart from 'chart.js';
|
||||||
|
|
||||||
|
import { banners } from '../../data/banners';
|
||||||
|
|
||||||
|
import Icon from '../../components/Icon.svelte';
|
||||||
|
import TableHeader from '../../components/Table/TableHeader.svelte';
|
||||||
|
import WishDetailModal from './_detail.svelte';
|
||||||
|
|
||||||
|
import { characters } from '../../data/characters';
|
||||||
|
import { weaponList } from '../../data/weaponList';
|
||||||
|
import { getAccountPrefix } from '../../stores/account';
|
||||||
|
import { fromRemote, readSave } from '../../stores/saveManager';
|
||||||
|
|
||||||
|
Chart.defaults.global.defaultFontColor = '#cbd5e0';
|
||||||
|
Chart.defaults.global.defaultFontFamily = 'Poppins';
|
||||||
|
|
||||||
|
let numberFormat = Intl.NumberFormat();
|
||||||
|
|
||||||
|
const { open: openModal } = getContext('simple-modal');
|
||||||
|
|
||||||
|
export let id;
|
||||||
|
|
||||||
|
const bannerTypes = {
|
||||||
|
'character-event': 'characters',
|
||||||
|
'weapon-event': 'weapons',
|
||||||
|
standard: 'standard',
|
||||||
|
beginners: 'beginners',
|
||||||
|
};
|
||||||
|
|
||||||
|
const bannerType = bannerTypes[id];
|
||||||
|
let bannerChart;
|
||||||
|
let pieChart;
|
||||||
|
|
||||||
|
let loading = true;
|
||||||
|
let pullData = [];
|
||||||
|
let pulls = [];
|
||||||
|
let sorted = [];
|
||||||
|
let total = 0;
|
||||||
|
let legendary = 0;
|
||||||
|
let rare = 0;
|
||||||
|
|
||||||
|
let allLegendary = [];
|
||||||
|
let allRare = [];
|
||||||
|
|
||||||
|
let sortBy = 'time';
|
||||||
|
let sortOrder;
|
||||||
|
|
||||||
|
let currentBannerIndex = -1;
|
||||||
|
let selectedBanners;
|
||||||
|
|
||||||
|
selectedBanners = banners[bannerType].map((e) => {
|
||||||
|
const start = dayjs(e.start, 'YYYY-MM-DD HH:mm:ss');
|
||||||
|
const end = dayjs(e.end, 'YYYY-MM-DD HH:mm:ss');
|
||||||
|
const image = `/images/banners/${e.name} ${start.format('YYYY-MM-DD')}.png`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...e,
|
||||||
|
start: start.unix(),
|
||||||
|
end: end.unix(),
|
||||||
|
image,
|
||||||
|
total: 0,
|
||||||
|
legendary: [],
|
||||||
|
rare: {
|
||||||
|
character: [],
|
||||||
|
weapon: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function openDetail(banner) {
|
||||||
|
openModal(
|
||||||
|
WishDetailModal,
|
||||||
|
{
|
||||||
|
banner,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
closeButton: false,
|
||||||
|
styleWindow: { background: '#25294A', width: '600px' },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readLocalData() {
|
||||||
|
console.log('wish read local');
|
||||||
|
const prefix = getAccountPrefix();
|
||||||
|
const data = readSave(`${prefix}${path}`);
|
||||||
|
if (data !== null) {
|
||||||
|
const counterData = JSON.parse(data);
|
||||||
|
total = counterData.total;
|
||||||
|
legendary = counterData.legendary;
|
||||||
|
rare = counterData.rare;
|
||||||
|
pullData = counterData.pulls || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
processPullData();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNextBanner(time) {
|
||||||
|
for (let i = currentBannerIndex + 1; i < selectedBanners.length; i++) {
|
||||||
|
console.log('change banner', i, dayjs.unix(time).format(), dayjs.unix(selectedBanners[i].start).format());
|
||||||
|
if (time >= selectedBanners[i].start && time < selectedBanners[i].end) {
|
||||||
|
currentBannerIndex = i;
|
||||||
|
return selectedBanners[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processPullData() {
|
||||||
|
const currentPulls = [];
|
||||||
|
console.log(selectedBanners);
|
||||||
|
|
||||||
|
let currentBanner = null;
|
||||||
|
let grouped = false;
|
||||||
|
let striped = false;
|
||||||
|
let startBanner = false;
|
||||||
|
for (let i = 0; i < pullData.length; i++) {
|
||||||
|
const pull = pullData[i];
|
||||||
|
const next = pullData[i + 1] || { time: dayjs().year(2000).unix() };
|
||||||
|
|
||||||
|
if (currentBanner === null || currentBanner.end < pull.time) {
|
||||||
|
currentBanner = getNextBanner(pull.time);
|
||||||
|
startBanner = true;
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
currentPulls[i - 1].end = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const item =
|
||||||
|
pull.type === 'character'
|
||||||
|
? characters[pull.id]
|
||||||
|
: pull.type === 'weapon'
|
||||||
|
? weaponList[pull.id]
|
||||||
|
: { name: 'Unknown', rarity: 3 };
|
||||||
|
|
||||||
|
selectedBanners[currentBannerIndex].total++;
|
||||||
|
|
||||||
|
const newPull = {
|
||||||
|
...pull,
|
||||||
|
formattedTime: formatTime(pull.time),
|
||||||
|
name: item.name,
|
||||||
|
rarity: item.rarity,
|
||||||
|
banner: currentBanner,
|
||||||
|
start: startBanner,
|
||||||
|
at: selectedBanners[currentBannerIndex].total,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (item.rarity === 5) {
|
||||||
|
selectedBanners[currentBannerIndex].legendary.push(newPull);
|
||||||
|
allLegendary.push(newPull);
|
||||||
|
} else if (item.rarity === 4) {
|
||||||
|
allRare.push(newPull);
|
||||||
|
if (pull.type === 'character') {
|
||||||
|
selectedBanners[currentBannerIndex].rare.character.push(newPull);
|
||||||
|
} else if (pull.type === 'weapon') {
|
||||||
|
selectedBanners[currentBannerIndex].rare.weapon.push(newPull);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!grouped && pull.time === next.time) {
|
||||||
|
striped = !striped;
|
||||||
|
newPull.group = 'start';
|
||||||
|
grouped = true;
|
||||||
|
} else if (grouped && pull.time !== next.time) {
|
||||||
|
newPull.group = 'end';
|
||||||
|
grouped = false;
|
||||||
|
} else if (grouped) {
|
||||||
|
newPull.group = 'group';
|
||||||
|
} else {
|
||||||
|
striped = !striped;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i === pullData.length - 1) {
|
||||||
|
newPull.end = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
newPull.striped = striped;
|
||||||
|
startBanner = false;
|
||||||
|
|
||||||
|
currentPulls.push(newPull);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(currentPulls.slice());
|
||||||
|
pulls = currentPulls;
|
||||||
|
sorted = pulls.reverse();
|
||||||
|
|
||||||
|
let labels = [];
|
||||||
|
let totalEachBanner = [];
|
||||||
|
let totalLegendaryEachBanner = [];
|
||||||
|
let totalRareEachBanner = [];
|
||||||
|
let backgrounds = [];
|
||||||
|
let borders = [];
|
||||||
|
for (let e of selectedBanners) {
|
||||||
|
const curLegendary = e.legendary.length;
|
||||||
|
const curRare = e.rare.character.length + e.rare.weapon.length;
|
||||||
|
const curLeft = e.total - curLegendary - curRare;
|
||||||
|
|
||||||
|
labels.push(e.shortName);
|
||||||
|
totalEachBanner.push(curLeft);
|
||||||
|
totalLegendaryEachBanner.push(curLegendary);
|
||||||
|
totalRareEachBanner.push(curRare);
|
||||||
|
borders.push(e.color);
|
||||||
|
|
||||||
|
const rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e.color || '#ffffff');
|
||||||
|
backgrounds.push(`rgba(${parseInt(rgb[1], 16)}, ${parseInt(rgb[2], 16)}, ${parseInt(rgb[3], 16)}, 0.7)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(totalEachBanner, totalLegendaryEachBanner, totalRareEachBanner);
|
||||||
|
|
||||||
|
loading = false;
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
new Chart(pieChart, {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
labels: ['Total Pulls', 'Total 5*', 'Total 4*'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'total',
|
||||||
|
data: [currentPulls.length, allLegendary.length, allRare.length],
|
||||||
|
backgroundColor: ['rgba(107, 161, 192, 0.7)', 'rgba(255, 177, 63, 0.7)', 'rgba(210, 143, 214, 0.7)'],
|
||||||
|
borderColor: ['#6BA1C0', '#FFB13F', '#D28FD6'],
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
mode: 'dataset',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
new Chart(bannerChart, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: '5* pulls',
|
||||||
|
data: totalLegendaryEachBanner,
|
||||||
|
backgroundColor: 'rgba(255, 177, 63, 0.7)',
|
||||||
|
borderColor: '#FFB13F',
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '4* pulls',
|
||||||
|
data: totalRareEachBanner,
|
||||||
|
backgroundColor: 'rgba(210, 143, 214, 0.7)',
|
||||||
|
borderColor: '#D28FD6',
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Total pulls',
|
||||||
|
data: totalEachBanner,
|
||||||
|
backgroundColor: backgrounds,
|
||||||
|
borderColor: borders,
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false,
|
||||||
|
callbacks: {
|
||||||
|
title: (tooltipItem) => {
|
||||||
|
return selectedBanners[tooltipItem[0].index].name;
|
||||||
|
},
|
||||||
|
label: (tooltipItem, data) => {
|
||||||
|
console.log(tooltipItem, data);
|
||||||
|
const label = data.datasets[tooltipItem.datasetIndex].label;
|
||||||
|
const value =
|
||||||
|
tooltipItem.datasetIndex === 2
|
||||||
|
? data.datasets[0].data[tooltipItem.index] +
|
||||||
|
data.datasets[1].data[tooltipItem.index] +
|
||||||
|
data.datasets[2].data[tooltipItem.index]
|
||||||
|
: tooltipItem.value;
|
||||||
|
return `${label}: ${value}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
yAxes: [{ stacked: true, gridLines: { color: '#2d3748' } }],
|
||||||
|
xAxes: [{ stacked: true }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortPulls() {
|
||||||
|
if (sortBy === 'time') {
|
||||||
|
if (sortOrder) {
|
||||||
|
sorted = pulls.slice().reverse();
|
||||||
|
} else {
|
||||||
|
sorted = pulls;
|
||||||
|
}
|
||||||
|
} else if (sortBy === 'type') {
|
||||||
|
if (sortOrder) {
|
||||||
|
sorted = pulls.slice().sort((a, b) => a.type.localeCompare(b.type));
|
||||||
|
} else {
|
||||||
|
sorted = pulls.slice().sort((a, b) => b.type.localeCompare(a.type));
|
||||||
|
}
|
||||||
|
} else if (sortBy === 'rare') {
|
||||||
|
if (sortOrder) {
|
||||||
|
sorted = pulls.slice().sort((a, b) => a.rarity - b.rarity);
|
||||||
|
} else {
|
||||||
|
sorted = pulls.slice().sort((a, b) => b.rarity - a.rarity);
|
||||||
|
}
|
||||||
|
} else if (sortBy === 'pity') {
|
||||||
|
if (sortOrder) {
|
||||||
|
sorted = pulls.slice().sort((a, b) => a.pity - b.pity);
|
||||||
|
} else {
|
||||||
|
sorted = pulls.slice().sort((a, b) => b.pity - a.pity);
|
||||||
|
}
|
||||||
|
} else if (sortBy === 'name') {
|
||||||
|
if (sortOrder) {
|
||||||
|
sorted = pulls.slice().sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
} else {
|
||||||
|
sorted = pulls.slice().sort((a, b) => b.name.localeCompare(a.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
readLocalData();
|
||||||
|
});
|
||||||
|
|
||||||
|
function sort(by) {
|
||||||
|
if (sortBy === by) {
|
||||||
|
sortOrder = !sortOrder;
|
||||||
|
} else {
|
||||||
|
sortBy = by;
|
||||||
|
sortOrder = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sortPulls();
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(time) {
|
||||||
|
return dayjs.unix(time).format('ddd YYYY-MM-DD HH:mm:ss');
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateLegendaryColor(percentage) {
|
||||||
|
const hue = percentage * 120;
|
||||||
|
return `color: hsl(${hue}, 100%, 60%);`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateRareColor(percentage) {
|
||||||
|
const opacity = percentage + 0.3;
|
||||||
|
return `opacity: ${opacity};`;
|
||||||
|
}
|
||||||
|
|
||||||
|
$: path = `wish-counter-${id}`;
|
||||||
|
$: if ($fromRemote) {
|
||||||
|
readLocalData();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="pt-20 lg:ml-64 lg:pt-8">
|
||||||
|
<div class="flex items-center text-gray-400 px-4 md:px-8">
|
||||||
|
<a href="/wish" class="pr-2">
|
||||||
|
<Icon path={mdiArrowLeft} size={1.2} />
|
||||||
|
</a>
|
||||||
|
<h2 class="font-display font-bold text-2xl text-gray-400 flex-1">
|
||||||
|
Wish Counter
|
||||||
|
<span class="text-white">{$t(`wish.types.${id}`)}</span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
{#if loading}
|
||||||
|
<div class="text-white pl-4 md:pl-8 mt-4">Loading...</div>
|
||||||
|
{:else}
|
||||||
|
<div class="flex mt-4 wrapper">
|
||||||
|
<div class="block overflow-x-auto xl:overflow-x-visible whitespace-no-wrap px">
|
||||||
|
<div class="pr-4 pl-4 md:pl-8 xl:pr-2 table">
|
||||||
|
<table
|
||||||
|
class="{sortBy === 'time'
|
||||||
|
? 'list-table'
|
||||||
|
: ''} w-full block pl-4 pr-4 py-2 md:pl-8 md:py-4 bg-item rounded-xl"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<TableHeader
|
||||||
|
className="sticky top-0 bg-item z-30"
|
||||||
|
styleList="top: -1px;"
|
||||||
|
on:click={() => sort('time')}
|
||||||
|
sort={sortBy === 'time'}
|
||||||
|
order={sortOrder}
|
||||||
|
align="left"
|
||||||
|
>
|
||||||
|
{$t('wish.detail.time')}
|
||||||
|
<div class="absolute h-full w-8 bg-item" style="left: -2rem; top: -1px;" />
|
||||||
|
</TableHeader>
|
||||||
|
<TableHeader
|
||||||
|
className="sticky top-0 bg-item z-30"
|
||||||
|
styleList="top: -1px;"
|
||||||
|
on:click={() => sort('pity')}
|
||||||
|
sort={sortBy === 'pity'}
|
||||||
|
order={sortOrder}
|
||||||
|
align="center"
|
||||||
|
padding="px-2"
|
||||||
|
>
|
||||||
|
{$t('wish.detail.pity')}
|
||||||
|
</TableHeader>
|
||||||
|
<TableHeader
|
||||||
|
className="sticky top-0 bg-item z-30"
|
||||||
|
styleList="top: -1px;"
|
||||||
|
on:click={() => sort('name')}
|
||||||
|
sort={sortBy === 'name'}
|
||||||
|
order={sortOrder}
|
||||||
|
align="left"
|
||||||
|
>
|
||||||
|
{$t('wish.detail.name')}
|
||||||
|
</TableHeader>
|
||||||
|
<TableHeader
|
||||||
|
className="sticky top-0 bg-item z-30"
|
||||||
|
styleList="top: -1px;"
|
||||||
|
on:click={() => sort('rare')}
|
||||||
|
sort={sortBy === 'rare'}
|
||||||
|
order={sortOrder}
|
||||||
|
align="center"
|
||||||
|
padding="px-2"
|
||||||
|
>
|
||||||
|
<Icon path={mdiStar} className="pb-1" />
|
||||||
|
</TableHeader>
|
||||||
|
<TableHeader
|
||||||
|
className="sticky top-0 bg-item z-30"
|
||||||
|
styleList="top: -1px;"
|
||||||
|
on:click={() => sort('type')}
|
||||||
|
sort={sortBy === 'type'}
|
||||||
|
order={sortOrder}
|
||||||
|
align="left"
|
||||||
|
>
|
||||||
|
{$t('wish.detail.type')}
|
||||||
|
</TableHeader>
|
||||||
|
<TableHeader className="sticky top-0 bg-item" styleList="top: -1px;" align="center">
|
||||||
|
{$t('wish.detail.roll')}
|
||||||
|
</TableHeader>
|
||||||
|
<TableHeader
|
||||||
|
className="sticky top-0 bg-item z-30 {sortBy === 'time' ? 'xl:hidden' : ''}"
|
||||||
|
styleList="top: -1px;"
|
||||||
|
align="left"
|
||||||
|
>
|
||||||
|
{$t('wish.detail.banner')}
|
||||||
|
</TableHeader>
|
||||||
|
</tr>
|
||||||
|
{#each sorted as pull}
|
||||||
|
<tr class="rarity-{pull.rarity}{pull.striped && sortBy === 'time' ? ' striped' : ''}">
|
||||||
|
<td
|
||||||
|
class="border-t border-gray-700 px-4 text-gray-200 whitespace-no-wrap relative"
|
||||||
|
style="font-family: monospace;"
|
||||||
|
>
|
||||||
|
{pull.formattedTime}
|
||||||
|
{#if sortBy === 'time' && (sortOrder ? pull.group === 'start' : pull.group === 'end')}
|
||||||
|
<div class="group-bar"><span>x10</span></div>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
<td class="border-t border-gray-700 px-2 text-gray-200 text-center">
|
||||||
|
<span
|
||||||
|
style={pull.rarity === 5
|
||||||
|
? calculateLegendaryColor((90 - pull.pity) / 90)
|
||||||
|
: calculateRareColor((10 - pull.pity) / 10)}
|
||||||
|
>
|
||||||
|
{pull.pity}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="border-t border-gray-700 pl-4 text-gray-200 flex items-center">
|
||||||
|
<img
|
||||||
|
class="w-8 h-8 mr-2"
|
||||||
|
src={pull.type === 'character'
|
||||||
|
? `/images/characters/${pull.id}.png`
|
||||||
|
: pull.type === 'weapon'
|
||||||
|
? `/images/weapons/${pull.id}.png`
|
||||||
|
: '/images/wish.png'}
|
||||||
|
alt={pull.name}
|
||||||
|
/>
|
||||||
|
<span class="pr-4">{pull.name}</span>
|
||||||
|
</td>
|
||||||
|
<td class="border-t border-gray-700 px-2 text-gray-200 text-center">{pull.rarity}</td>
|
||||||
|
<td class="border-t border-gray-700 px-4 text-gray-200">
|
||||||
|
{$t(`wish.detail.${pull.type}`)}
|
||||||
|
</td>
|
||||||
|
<td class="border-t border-gray-700 px-2 text-gray-200 text-center">
|
||||||
|
{pull.at}
|
||||||
|
</td>
|
||||||
|
{#if sortBy === 'time' && ((pull.end && !sortOrder) || (pull.start && sortOrder))}
|
||||||
|
<td class="relative hidden xl:table-cell">
|
||||||
|
<div class="border-t border-gray-700 absolute top-0 z-10" style="width: 266px;" />
|
||||||
|
</td>
|
||||||
|
<td class="sticky w-0 hidden xl:table-cell pl-2" style="top: 8px;">
|
||||||
|
<div
|
||||||
|
class="w-64 absolute top-0 pt-2 bg-item cursor-pointer"
|
||||||
|
on:click={() => openDetail(pull.banner)}
|
||||||
|
>
|
||||||
|
<img class="w-full rounded-lg" src={pull.banner.image} alt={pull.banner.name} />
|
||||||
|
<p class="bg-gray-900 rounded-lg mt-2 text-center text-gray-200">
|
||||||
|
{pull.banner.total} Pulls
|
||||||
|
<img class="h-4 inline ml-2" src="/images/primogem.png" alt="primogem" />
|
||||||
|
{numberFormat.format(pull.banner.total * 160)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{/if}
|
||||||
|
<td
|
||||||
|
class="border-t border-gray-700 px-4 text-gray-200 top-0 text-center {sortBy === 'time'
|
||||||
|
? 'xl:hidden'
|
||||||
|
: ''}"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
on:click={() => openDetail(pull.banner)}
|
||||||
|
class="h-8 inline cursor-pointer"
|
||||||
|
src={pull.banner.image}
|
||||||
|
alt={pull.banner.name}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-area flex flex-wrap">
|
||||||
|
<div class="flex">
|
||||||
|
<div
|
||||||
|
class="bg-background px-4 py-2 rounded-xl flex flex-col items-center justify-center mr-4"
|
||||||
|
style="height: 200px;"
|
||||||
|
>
|
||||||
|
<p class="text-gray-400 font-body">Total</p>
|
||||||
|
<p class="text-gray-400 font-body text-xl font-bold">{total}</p>
|
||||||
|
<p class="text-gray-400 font-body mt-2 flex items-center">5<Icon size={0.7} path={mdiStar} /> Pity</p>
|
||||||
|
<p class="text-legendary-from font-body text-xl font-bold">{legendary}</p>
|
||||||
|
<p class="text-gray-400 font-body mt-2 flex items-center">4<Icon size={0.7} path={mdiStar} /> Pity</p>
|
||||||
|
<p class="text-rare-from font-body text-xl font-bold">{rare}</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-background rounded-xl inline-block mb-4 p-2 pie-chart mr-4">
|
||||||
|
<canvas width="200" height="200" bind:this={pieChart} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if id === 'character-event' || id === 'weapon-event'}
|
||||||
|
<div class="bg-background rounded-xl inline-block mb-4 p-2 banner-chart">
|
||||||
|
<canvas width="500" height="200" bind:this={bannerChart} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
@apply flex-col-reverse;
|
||||||
|
|
||||||
|
.chart-area {
|
||||||
|
@apply px-4;
|
||||||
|
|
||||||
|
@screen md {
|
||||||
|
@apply px-8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1920px) {
|
||||||
|
@apply flex-row;
|
||||||
|
|
||||||
|
.chart-area {
|
||||||
|
@apply px-2;
|
||||||
|
@apply flex-col;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-chart {
|
||||||
|
max-width: 500px;
|
||||||
|
height: 200px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-chart {
|
||||||
|
max-width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.striped {
|
||||||
|
background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.4) 20%, rgba(0, 0, 0, 0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.rarity-5 {
|
||||||
|
background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(185, 129, 46, 0.55) 20%, rgba(0, 0, 0, 0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.rarity-4 {
|
||||||
|
background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(173, 118, 176, 0.55) 20%, rgba(0, 0, 0, 0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-bar {
|
||||||
|
width: 1px;
|
||||||
|
height: 317px;
|
||||||
|
left: 0;
|
||||||
|
top: 17px;
|
||||||
|
@apply bg-white;
|
||||||
|
@apply absolute;
|
||||||
|
@apply select-none;
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
width: 8px;
|
||||||
|
height: 1px;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
@apply bg-white;
|
||||||
|
@apply absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
top: initial;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
top: 155px;
|
||||||
|
left: -20px;
|
||||||
|
@apply absolute;
|
||||||
|
@apply transform;
|
||||||
|
@apply -rotate-90;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.list-table {
|
||||||
|
@screen xl {
|
||||||
|
padding-right: 17rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import { onMount, getContext } from 'svelte';
|
import { onMount, getContext } from 'svelte';
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
import { mdiPencil, mdiStar, mdiChevronDown } from '@mdi/js';
|
import { mdiPencil, mdiStar, mdiChevronDown, mdiTableOfContents } from '@mdi/js';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
const { open: openModal, close: closeModal } = getContext('simple-modal');
|
const { open: openModal, close: closeModal } = getContext('simple-modal');
|
||||||
|
@ -245,13 +245,18 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="bg-item rounded-xl p-4 inline-flex flex-col w-full" style="height: min-content;">
|
<div class="bg-item rounded-xl p-4 inline-flex flex-col w-full" style="height: min-content;">
|
||||||
<div class="flex justify-between mb-2">
|
<div class="flex mb-2">
|
||||||
<h2 class="font-display font-bold text-2xl text-white">{name}</h2>
|
<h2 class="font-display font-bold text-2xl text-white flex-1">{name}</h2>
|
||||||
{#if manualInput}
|
{#if manualInput}
|
||||||
<Button size="sm" on:click={toggleEdit}>
|
<Button size="sm" on:click={toggleEdit}>
|
||||||
<Icon path={mdiPencil} color="white" />
|
<Icon path={mdiPencil} color="white" />
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
|
<a href="/wish/{id}">
|
||||||
|
<Button className="ml-2" size="sm">
|
||||||
|
<Icon path={mdiTableOfContents} color="white" />
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col w-full">
|
<div class="flex flex-col w-full">
|
||||||
<div
|
<div
|
||||||
|
|
146
src/routes/wish/_detail.svelte
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
<script>
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import Icon from '../../components/Icon.svelte';
|
||||||
|
import { mdiStar } from '@mdi/js';
|
||||||
|
|
||||||
|
let numberFormat = Intl.NumberFormat('en', {
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export let banner;
|
||||||
|
|
||||||
|
function calculateColor(percentage) {
|
||||||
|
const hue = percentage * 120;
|
||||||
|
return `color: hsl(${hue}, 100%, 60%);`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const legendaryPity = banner.legendary.reduce((prev, next) => {
|
||||||
|
prev += next.pity;
|
||||||
|
return prev;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
let rarePity = 0;
|
||||||
|
let rarePityCharacter = 0;
|
||||||
|
let rarePityWeapon = 0;
|
||||||
|
let rareTotal = banner.rare.character.length + banner.rare.weapon.length;
|
||||||
|
|
||||||
|
for (let item of banner.rare.character) {
|
||||||
|
rarePity += item.pity;
|
||||||
|
rarePityCharacter += item.pity;
|
||||||
|
}
|
||||||
|
for (let item of banner.rare.weapon) {
|
||||||
|
rarePity += item.pity;
|
||||||
|
rarePityWeapon += item.pity;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<img src={banner.image} class="w-full rounded-lg" alt={banner.name} />
|
||||||
|
<h1 class="mt-4 text-white font-display font-semibold text-xl">{banner.name}</h1>
|
||||||
|
<p class="text-gray-400 font-body flex flex-col md:flex-row">
|
||||||
|
<span class="flex">
|
||||||
|
<span>{dayjs.unix(banner.start).format('ddd, D MMM YYYY HH:mm')}</span>
|
||||||
|
<span class="mx-2">-</span>
|
||||||
|
</span>
|
||||||
|
<span>{dayjs.unix(banner.end).format('ddd, D MMM YYYY HH:mm')}</span>
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-400 pr-2 mt-4">
|
||||||
|
{$t('wish.detail.totalThisBanner')}
|
||||||
|
<span class="text-gray-200 font-semibold">{banner.total}</span>
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-400 pr-2">
|
||||||
|
{$t('wish.detail.worth')}
|
||||||
|
<img class="inline h-4" src="/images/primogem.png" alt="primogem" />
|
||||||
|
<span class="text-gray-200 font-semibold">{banner.total * 160}</span>
|
||||||
|
</p>
|
||||||
|
<table class="mt-4">
|
||||||
|
<tr>
|
||||||
|
<td class="text-gray-400 text-sm font-display pr-2 md:pr-4 text-left">Rarity</td>
|
||||||
|
<td class="text-gray-400 text-sm font-display pr-2 md:pr-4 text-right">Total</td>
|
||||||
|
<td class="text-gray-400 text-sm font-display pr-2 md:pr-4 text-right">Percent</td>
|
||||||
|
<td class="text-gray-400 text-sm font-display text-right whitespace-no-wrap">Pity AVG</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-legendary-from font-semibold pr-2 md:pr-4 border-t border-gray-700">
|
||||||
|
5 <Icon path={mdiStar} color="#FFB13F" size="0.6" />
|
||||||
|
</td>
|
||||||
|
<td class="text-legendary-from font-semibold pr-2 md:pr-4 text-right border-t border-gray-700">
|
||||||
|
{numberFormat.format(banner.legendary.length)}
|
||||||
|
</td>
|
||||||
|
<td class="text-legendary-from font-semibold pr-2 md:pr-4 text-right border-t border-gray-700">
|
||||||
|
{numberFormat.format((banner.legendary.length / banner.total) * 100)}%
|
||||||
|
</td>
|
||||||
|
<td class="text-legendary-from font-semibold text-right border-t border-gray-700">
|
||||||
|
{banner.legendary.length ? numberFormat.format(legendaryPity / banner.legendary.length) : 0}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-rare-from font-semibold pr-2 md:pr-4 border-t border-gray-700">
|
||||||
|
4 <Icon path={mdiStar} color="#AD76B0" size="0.6" />
|
||||||
|
</td>
|
||||||
|
<td class="text-rare-from font-semibold pr-2 md:pr-4 text-right border-t border-gray-700">
|
||||||
|
{numberFormat.format(rareTotal)}
|
||||||
|
</td>
|
||||||
|
<td class="text-rare-from font-semibold pr-2 md:pr-4 text-right border-t border-gray-700">
|
||||||
|
{numberFormat.format((rareTotal / banner.total) * 100)}%
|
||||||
|
</td>
|
||||||
|
<td class="text-rare-from font-semibold text-right border-t border-gray-700">
|
||||||
|
{rareTotal > 0 ? numberFormat.format(rarePity / rareTotal) : 0}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-rare-from font-semibold pl-4 md:pl-4 pr-2 md:pr-4 border-t border-gray-700 whitespace-no-wrap">
|
||||||
|
└ Character
|
||||||
|
</td>
|
||||||
|
<td class="text-rare-from font-semibold pr-2 md:pr-4 text-right border-t border-gray-700">
|
||||||
|
{numberFormat.format(banner.rare.character.length)}
|
||||||
|
</td>
|
||||||
|
<td class="text-rare-from font-semibold pr-2 md:pr-4 text-right border-t border-gray-700">
|
||||||
|
{numberFormat.format((banner.rare.character.length / banner.total) * 100)}%
|
||||||
|
</td>
|
||||||
|
<td class="text-rare-from font-semibold text-right border-t border-gray-700">
|
||||||
|
{rareTotal > 0 ? numberFormat.format(rarePityCharacter / rareTotal) : 0}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-rare-from font-semibold pl-4 md:pl-4 pr-2 md:pr-4 border-t border-gray-700 whitespace-no-wrap">
|
||||||
|
└ Weapon
|
||||||
|
</td>
|
||||||
|
<td class="text-rare-from font-semibold pr-2 md:pr-4 text-right border-t border-gray-700">
|
||||||
|
{numberFormat.format(banner.rare.weapon.length)}
|
||||||
|
</td>
|
||||||
|
<td class="text-rare-from font-semibold pr-2 md:pr-4 text-right border-t border-gray-700">
|
||||||
|
{numberFormat.format((banner.rare.weapon.length / banner.total) * 100)}%
|
||||||
|
</td>
|
||||||
|
<td class="text-rare-from font-semibold text-right border-t border-gray-700">
|
||||||
|
{rareTotal > 0 ? numberFormat.format(rarePityWeapon / rareTotal) : 0}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="flex flex-wrap mt-4">
|
||||||
|
{#each banner.legendary as pull}
|
||||||
|
<span class="pity">{pull.name} <span style={calculateColor((90 - pull.pity) / 90)}>{pull.pity}</span></span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
span.pity {
|
||||||
|
@apply rounded-xl;
|
||||||
|
@apply text-gray-400;
|
||||||
|
@apply border;
|
||||||
|
@apply border-legendary-from;
|
||||||
|
@apply whitespace-no-wrap;
|
||||||
|
@apply px-2;
|
||||||
|
@apply mb-1;
|
||||||
|
@apply mr-1;
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
@apply font-semibold;
|
||||||
|
@apply pl-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
BIN
static/images/banners/Adrift in the Harbor 2021-01-12.png
Normal file
After Width: | Height: | Size: 489 KiB |
BIN
static/images/banners/Ballad in Goblets 2020-09-28.png
Normal file
After Width: | Height: | Size: 478 KiB |
BIN
static/images/banners/Ballad in Goblets 2021-03-17.png
Normal file
After Width: | Height: | Size: 515 KiB |
BIN
static/images/banners/Beginners' Wish 2000-01-01.png
Normal file
After Width: | Height: | Size: 286 KiB |
BIN
static/images/banners/Dance of Lanterns 2021-02-17.png
Normal file
After Width: | Height: | Size: 596 KiB |
BIN
static/images/banners/Epitome Invocation 2020-09-28.png
Normal file
After Width: | Height: | Size: 391 KiB |
BIN
static/images/banners/Epitome Invocation 2020-10-20.png
Normal file
After Width: | Height: | Size: 387 KiB |
BIN
static/images/banners/Epitome Invocation 2020-11-11.png
Normal file
After Width: | Height: | Size: 447 KiB |
BIN
static/images/banners/Epitome Invocation 2020-12-01.png
Normal file
After Width: | Height: | Size: 417 KiB |
BIN
static/images/banners/Epitome Invocation 2020-12-23.png
Normal file
After Width: | Height: | Size: 405 KiB |
BIN
static/images/banners/Epitome Invocation 2021-01-12.png
Normal file
After Width: | Height: | Size: 450 KiB |
BIN
static/images/banners/Epitome Invocation 2021-02-03.png
Normal file
After Width: | Height: | Size: 364 KiB |
BIN
static/images/banners/Epitome Invocation 2021-02-23.png
Normal file
After Width: | Height: | Size: 356 KiB |
BIN
static/images/banners/Epitome Invocation 2021-03-17.png
Normal file
After Width: | Height: | Size: 393 KiB |
BIN
static/images/banners/Farewell of Snezhnaya 2020-11-11.png
Normal file
After Width: | Height: | Size: 508 KiB |
BIN
static/images/banners/Gentry of Hermitage 2020-12-01.png
Normal file
After Width: | Height: | Size: 297 KiB |
BIN
static/images/banners/Invitation to Mundane Life 2021-02-03.png
Normal file
After Width: | Height: | Size: 564 KiB |
BIN
static/images/banners/Moment of Bloom 2021-03-02.png
Normal file
After Width: | Height: | Size: 513 KiB |
BIN
static/images/banners/Secretum Secretorum 2020-12-23.png
Normal file
After Width: | Height: | Size: 529 KiB |
BIN
static/images/banners/Sparkling Steps 2020-10-20.png
Normal file
After Width: | Height: | Size: 467 KiB |
BIN
static/images/banners/Wanderlust Invocation 2000-01-01.png
Normal file
After Width: | Height: | Size: 418 KiB |
30
yarn.lock
|
@ -1163,6 +1163,29 @@ chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
|
||||||
ansi-styles "^4.1.0"
|
ansi-styles "^4.1.0"
|
||||||
supports-color "^7.1.0"
|
supports-color "^7.1.0"
|
||||||
|
|
||||||
|
chart.js@^2.9.4:
|
||||||
|
version "2.9.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.4.tgz#0827f9563faffb2dc5c06562f8eb10337d5b9684"
|
||||||
|
integrity sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==
|
||||||
|
dependencies:
|
||||||
|
chartjs-color "^2.1.0"
|
||||||
|
moment "^2.10.2"
|
||||||
|
|
||||||
|
chartjs-color-string@^0.6.0:
|
||||||
|
version "0.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71"
|
||||||
|
integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==
|
||||||
|
dependencies:
|
||||||
|
color-name "^1.0.0"
|
||||||
|
|
||||||
|
chartjs-color@^2.1.0:
|
||||||
|
version "2.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.4.1.tgz#6118bba202fe1ea79dd7f7c0f9da93467296c3b0"
|
||||||
|
integrity sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==
|
||||||
|
dependencies:
|
||||||
|
chartjs-color-string "^0.6.0"
|
||||||
|
color-convert "^1.9.3"
|
||||||
|
|
||||||
clean-css@^4.2.1:
|
clean-css@^4.2.1:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78"
|
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78"
|
||||||
|
@ -1170,7 +1193,7 @@ clean-css@^4.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
source-map "~0.6.0"
|
source-map "~0.6.0"
|
||||||
|
|
||||||
color-convert@^1.9.0, color-convert@^1.9.1:
|
color-convert@^1.9.0, color-convert@^1.9.1, color-convert@^1.9.3:
|
||||||
version "1.9.3"
|
version "1.9.3"
|
||||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
||||||
|
@ -1874,6 +1897,11 @@ minimist@^1.1.1, minimist@^1.2.5:
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||||
|
|
||||||
|
moment@^2.10.2:
|
||||||
|
version "2.29.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||||
|
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
||||||
|
|
||||||
mri@^1.1.0:
|
mri@^1.1.0:
|
||||||
version "1.1.6"
|
version "1.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.6.tgz#49952e1044db21dbf90f6cd92bc9c9a777d415a6"
|
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.6.tgz#49952e1044db21dbf90f6cd92bc9c9a777d415a6"
|
||||||
|
|