First commit of os-league-tools-master
This commit is contained in:
109
os-league-tools-master/src/pages/About.js
Normal file
109
os-league-tools-master/src/pages/About.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import React from 'react';
|
||||
import Card from '../components/common/Card';
|
||||
import Separator from '../components/common/Separator';
|
||||
import PageWrapper from '../components/PageWrapper';
|
||||
|
||||
export default function About() {
|
||||
const emphasisedText = 'text-accent font-semibold';
|
||||
return (
|
||||
<PageWrapper>
|
||||
<div className='container lg:max-w-[768px] mx-auto'>
|
||||
<Card>
|
||||
<Card.Header className=''>
|
||||
<p className='text-accent font-bold text-center small-caps text-2xl tracking-widest'>OS-LEAGUE-TOOLS</p>
|
||||
<p className='text-accent font-semibold text-center'>
|
||||
open source trackers, tools, and more for Old School Runescape's seasonal leagues gamemode
|
||||
</p>
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<Separator />
|
||||
<Header>about</Header>
|
||||
<Paragraph>
|
||||
OS League Tools was originally launched for Trailblazer League by{' '}
|
||||
<Link text='chaiinchomp' href='https://github.com/chaiinchomp' />, a runescape veteran since 2005. After
|
||||
spending all of the first league making checklists and custom calculators in a spreadsheet, a website was
|
||||
the obvious next step.
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
A special massive shoutout goes to <Link text='perterter' href='https://github.com/tylerthardy' />, who
|
||||
developed the accompanying RuneLite plugin, without which I'm sure the site would never have taken off as
|
||||
much as it did.
|
||||
</Paragraph>
|
||||
<Separator />
|
||||
<Header>contributing</Header>
|
||||
<Paragraph>
|
||||
If you're interested in supporting the site's development, you can do so by (most importantly) continuing
|
||||
to use it and spread the word to others! This is a passion project built entirely by volunteers, so the
|
||||
biggest reward is just to see people using what we've created.
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
New code contributors are always welcome, too. Check out the source code on{' '}
|
||||
<Link text='github' href='https://github.com/osrs-reldo/os-league-tools' />, and come by the{' '}
|
||||
<span className='text-code'>#development</span> channel in{' '}
|
||||
<Link text='discord' href='https://discord.gg/GQ5kVyU' />
|
||||
to chat, ask questions, and see which features and bugs need help.
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
Finally, if you would like to support the site financially, you can drop a few bucks in the{' '}
|
||||
<Link text='tip jar' href='https://ko-fi.com/osleaguetools' />. All funds go directly into paying the
|
||||
site's hosting costs.
|
||||
</Paragraph>
|
||||
<Separator />
|
||||
<Header>credits</Header>
|
||||
<div className='m-2 grid md:grid-cols-4 grid-cols-2'>
|
||||
<span className={`${emphasisedText} whitespace-nowrapp md:text-right`}>developed using:</span>
|
||||
<ul className='col-span-3 list-disc text-sm mb-3 ml-6'>
|
||||
<li>React/JS with Tailwind CSS</li>
|
||||
<li>Additional libraries: osrs-json-hiscores</li>
|
||||
</ul>
|
||||
<span className={`${emphasisedText} whitespace-nowrap md:text-right`}>with help from:</span>
|
||||
<ul className='col-span-3 list-disc text-sm ml-6'>
|
||||
<li>
|
||||
All of the awesome people who have contributed code, bug reports, and feedback in the{' '}
|
||||
<Link text='discord' href='https://discord.gg/GQ5kVyU' newTab />
|
||||
</li>
|
||||
<li>
|
||||
Massive amount of data, images, and more from the{' '}
|
||||
<Link text='Official OSRS Wiki' href='https://oldschool.runescape.wiki/' />
|
||||
(with help figuring out how to parse the wiki API from{' '}
|
||||
<Link text='osrsbox' href='https://www.osrsbox.com/blog/2018/12/12/scraping-the-osrs-wiki-part1/' />)
|
||||
</li>
|
||||
<li>
|
||||
Lots of helpful research and data compilations from the folks in the{' '}
|
||||
<Link text='OSRS Leagues Discord' href='http://discord.osrs-leagues.com/' />
|
||||
</li>
|
||||
<li>
|
||||
Icons from <Link text='google fonts' href='https://fonts.google.com/icons' />
|
||||
(+ RuneLite icon from <Link text='runelite.net' href='https://runelite.net/' />)
|
||||
</li>
|
||||
<li>
|
||||
Some obscure numbers used in skill calculators from tweeting questions at{' '}
|
||||
<Link text='Mod Ash' href='https://twitter.com/JagexAsh' />
|
||||
</li>
|
||||
<li>
|
||||
...and all of you <span className={`icon-base inline align-sub ${emphasisedText}`}>favorite</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</div>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
function Header({ children }) {
|
||||
return <p className='text-accent font-semibold text-center tracking-widest m-2'>{children}</p>;
|
||||
}
|
||||
|
||||
function Link({ text, href }) {
|
||||
return (
|
||||
<a href={href} target='_blank' rel='noreferrer' className='hover:underline text-accent font-semibold'>
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function Paragraph({ children }) {
|
||||
return <p className='indent-8 m-2 text-sm'>{children}</p>;
|
||||
}
|
||||
16
os-league-tools-master/src/pages/BankedExp.js
Normal file
16
os-league-tools-master/src/pages/BankedExp.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import Card from '../components/common/Card';
|
||||
import PageWrapper from '../components/PageWrapper';
|
||||
import BankedExpPanel from './BankedExpPanel';
|
||||
|
||||
export default function BankedExp() {
|
||||
return (
|
||||
<PageWrapper>
|
||||
<Card>
|
||||
<Card.Body>
|
||||
<BankedExpPanel />
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
54
os-league-tools-master/src/pages/BankedExpPanel.js
Normal file
54
os-league-tools-master/src/pages/BankedExpPanel.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import React, { useState } from 'react';
|
||||
import useBreakpoint, { MEDIA_QUERIES, MODE } from '../hooks/useBreakpoint';
|
||||
import BankedExpSettings from '../components/BankedExpSettings';
|
||||
import BankedExpTable from '../components/BankedExpTable';
|
||||
import useMultipliers from '../hooks/useMultipliers';
|
||||
import useEquilibrium from '../hooks/useEquilibrium';
|
||||
|
||||
export default function BankedExpPanel() {
|
||||
const isSmViewport = useBreakpoint(MEDIA_QUERIES.SM, MODE.LESS_OR_EQ);
|
||||
const isXlViewport = useBreakpoint(MEDIA_QUERIES.XL);
|
||||
const [showSidebar, setShowSidebar] = useState(isXlViewport);
|
||||
const [expGained, setExpGained] = useState(0);
|
||||
const multipliersState = useMultipliers();
|
||||
const equilibriumState = useEquilibrium();
|
||||
|
||||
return (
|
||||
<section className='flex flex-col xl:flex-row w-full bg-secondary-alt xl:bg-primary'>
|
||||
{isSmViewport && showSidebar && (
|
||||
<div className='mt-3 bg-hover cursor-pointer' onClick={() => setShowSidebar(!showSidebar)}>
|
||||
<span className='icon-xl align-middle'>keyboard_double_arrow_up</span>
|
||||
<span className='text-sm italic ml-1'>Hide settings</span>
|
||||
</div>
|
||||
)}
|
||||
{showSidebar && (
|
||||
<div className='basis-[23%] p-2'>
|
||||
<BankedExpSettings
|
||||
expGained={expGained}
|
||||
multipliersState={multipliersState}
|
||||
equilibriumState={equilibriumState}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className='mt-3 mb-3 bg-hover cursor-pointer' onClick={() => setShowSidebar(!showSidebar)}>
|
||||
{isXlViewport ? (
|
||||
<span className='icon-xl align-middle'>
|
||||
{showSidebar ? 'keyboard_double_arrow_left' : 'keyboard_double_arrow_right'}
|
||||
</span>
|
||||
) : (
|
||||
<p className='text-sm italic ml-1'>
|
||||
<span className='icon-xl align-middle'>
|
||||
{showSidebar ? 'keyboard_double_arrow_up' : 'keyboard_double_arrow_down'}
|
||||
</span>
|
||||
{showSidebar ? 'Hide settings' : 'Show settings'}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<BankedExpTable
|
||||
setExpGained={setExpGained}
|
||||
multipliersState={multipliersState}
|
||||
equilibriumState={equilibriumState}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
26
os-league-tools-master/src/pages/Calculators.js
Normal file
26
os-league-tools-master/src/pages/Calculators.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import images from '../assets/images';
|
||||
import TabbedCard from '../components/common/TabbedCard';
|
||||
import PageWrapper from '../components/PageWrapper';
|
||||
import useFetchHiscoresOnLoad from '../hooks/useFetchHiscoresOnLoad';
|
||||
import useQueryString from '../hooks/useQueryString';
|
||||
import BankedExpPanel from './BankedExpPanel';
|
||||
import CalculatorsPanel from './CalculatorsPanel';
|
||||
|
||||
export default function Calculators() {
|
||||
const [selectedTab, onSetSelectedTab] = useQueryString('tab');
|
||||
useFetchHiscoresOnLoad();
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<TabbedCard defaultActiveTab={selectedTab} setTabCallback={onSetSelectedTab}>
|
||||
<TabbedCard.Tab id='character' label='Skill calculators' icon={images['tab-stats.png']}>
|
||||
<CalculatorsPanel />
|
||||
</TabbedCard.Tab>
|
||||
<TabbedCard.Tab id='bankedExp' label='Banked exp' icon={images['tab-inventory.png']}>
|
||||
<BankedExpPanel />
|
||||
</TabbedCard.Tab>
|
||||
</TabbedCard>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
55
os-league-tools-master/src/pages/CalculatorsPanel.js
Normal file
55
os-league-tools-master/src/pages/CalculatorsPanel.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import useBreakpoint, { MEDIA_QUERIES, MODE } from '../hooks/useBreakpoint';
|
||||
import CalculatorSettings from '../components/CalculatorSettings';
|
||||
import CalculatorTable from '../components/CalculatorTable';
|
||||
import useMultipliers from '../hooks/useMultipliers';
|
||||
import useEquilibrium from '../hooks/useEquilibrium';
|
||||
|
||||
export default function CalculatorsPanel() {
|
||||
const {
|
||||
calculators: { skill, expValues, baseMultiplier },
|
||||
} = useSelector(state => ({ calculators: state.calculators, tasks: state.tasks }));
|
||||
const isSmViewport = useBreakpoint(MEDIA_QUERIES.SM, MODE.LESS_OR_EQ);
|
||||
const isXlViewport = useBreakpoint(MEDIA_QUERIES.XL);
|
||||
const [showSidebar, setShowSidebar] = useState(isXlViewport);
|
||||
const multipliersState = useMultipliers();
|
||||
const equilibriumState = useEquilibrium();
|
||||
|
||||
return (
|
||||
<section className='flex flex-col xl:flex-row w-full bg-secondary-alt xl:bg-primary'>
|
||||
{isSmViewport && showSidebar && (
|
||||
<div className='mt-3 bg-hover cursor-pointer' onClick={() => setShowSidebar(!showSidebar)}>
|
||||
<span className='icon-xl align-middle'>keyboard_double_arrow_up</span>
|
||||
<span className='text-sm italic ml-1'>Hide settings</span>
|
||||
</div>
|
||||
)}
|
||||
{showSidebar && (
|
||||
<div className='basis-[23%] p-2'>
|
||||
<CalculatorSettings skill={skill} multipliersState={multipliersState} equilibriumState={equilibriumState} />
|
||||
</div>
|
||||
)}
|
||||
<div className='mt-3 mb-3 bg-hover cursor-pointer' onClick={() => setShowSidebar(!showSidebar)}>
|
||||
{isXlViewport ? (
|
||||
<span className='icon-xl align-middle'>
|
||||
{showSidebar ? 'keyboard_double_arrow_left' : 'keyboard_double_arrow_right'}
|
||||
</span>
|
||||
) : (
|
||||
<p className='text-sm italic ml-1'>
|
||||
<span className='icon-xl align-middle'>
|
||||
{showSidebar ? 'keyboard_double_arrow_up' : 'keyboard_double_arrow_down'}
|
||||
</span>
|
||||
{showSidebar ? 'Hide settings' : 'Show settings'}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<CalculatorTable
|
||||
skill={skill}
|
||||
expValues={expValues}
|
||||
baseMultiplier={baseMultiplier}
|
||||
multipliersState={multipliersState}
|
||||
equilibriumState={equilibriumState}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
59
os-league-tools-master/src/pages/CharacterPanel.js
Normal file
59
os-league-tools-master/src/pages/CharacterPanel.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import SkillsPanel from '../components/SkillsPanel';
|
||||
import BossesPanel from '../components/BossesPanel';
|
||||
import ManageCharactersModal from '../components/ManageCharactersModal';
|
||||
import CharacterRegionsSection from '../components/CharacterRegionsSection';
|
||||
import CharacterRelicsSection from '../components/CharacterRelicsSection';
|
||||
import CharacterTotalsSection from '../components/CharacterTotalsSection';
|
||||
|
||||
export default function CharacterPanel() {
|
||||
const [isCharacterModalOpen, setCharacterModalOpen] = useState(false);
|
||||
const characterState = useSelector(state => state.character);
|
||||
const username = characterState.characters[characterState.activeCharacter];
|
||||
const { taskStats, tier } = useSelector(state => state.tasks);
|
||||
const unlockState = useSelector(state => state.unlocks);
|
||||
|
||||
if (!username) {
|
||||
return (
|
||||
<div>
|
||||
<ManageCharactersModal
|
||||
isOpen={isCharacterModalOpen}
|
||||
setIsOpen={val => setCharacterModalOpen(val)}
|
||||
initialAddModalOpen
|
||||
/>
|
||||
<p className='text-accent text-center small-caps text-2xl'>No character found</p>
|
||||
<p className='font-semibold text-center'>
|
||||
To use the character tracker,{' '}
|
||||
<button className='text-accent hover:underline' type='button' onClick={() => setCharacterModalOpen(true)}>
|
||||
set your username
|
||||
</button>
|
||||
!
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-col w-full items-stretch'>
|
||||
<p className='text-center text-accent text-4xl font-mono uppercase tracking-widest'>{username}</p>
|
||||
<div className='flex md:flex-row flex-col lg:flex-nowrap flex-wrap justify-around w-full items-stretch md:gap-1 gap-3'>
|
||||
{/* LEFT COLUMN */}
|
||||
<div className='lg:basis-1/4 basis-2/5 flex flex-col h-full items-center gap-3 order-3 lg:order-1'>
|
||||
<CharacterTotalsSection taskStats={taskStats} />
|
||||
</div>
|
||||
{/* CENTER COLUMN */}
|
||||
<div className='lg:basis-1/2 basis-full flex flex-col items-center gap-3 order-1 lg:order-3 shrink'>
|
||||
<CharacterRegionsSection taskStats={taskStats} unlockedRegions={unlockState.regions} />
|
||||
<CharacterRelicsSection tier={tier} taskStats={taskStats} unlockedRelics={unlockState.relics} />
|
||||
</div>
|
||||
{/* RIGHT COLUMN */}
|
||||
<div className='lg:basis-1/4 basis-2/5 flex flex-col items-center order-5 gap-3'>
|
||||
<span className='text-lg text-accent font-semibold border-b border-accent w-full'>Stats</span>
|
||||
<SkillsPanel />
|
||||
<BossesPanel />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
43
os-league-tools-master/src/pages/DiariesPanel.js
Normal file
43
os-league-tools-master/src/pages/DiariesPanel.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React, { useState } from 'react';
|
||||
import DiariesFilters from '../components/DiariesFilters';
|
||||
import DiariesTable from '../components/DiariesTable';
|
||||
import useBreakpoint, { MEDIA_QUERIES, MODE } from '../hooks/useBreakpoint';
|
||||
|
||||
export default function DiariesPanel() {
|
||||
const isSmViewport = useBreakpoint(MEDIA_QUERIES.SM, MODE.LESS_OR_EQ);
|
||||
const isXlViewport = useBreakpoint(MEDIA_QUERIES.XL);
|
||||
const [showSidebar, setShowSidebar] = useState(isXlViewport);
|
||||
|
||||
return (
|
||||
<div className='flex flex-col xl:flex-row w-full bg-secondary-alt xl:bg-primary'>
|
||||
{isSmViewport && showSidebar && (
|
||||
<div className='mt-3 bg-hover cursor-pointer' onClick={() => setShowSidebar(!showSidebar)}>
|
||||
<span className='icon-xl align-middle'>keyboard_double_arrow_up</span>
|
||||
<span className='text-sm italic ml-1'>Hide filters</span>
|
||||
</div>
|
||||
)}
|
||||
{showSidebar && (
|
||||
<div className='basis-[23%] p-2'>
|
||||
<DiariesFilters />
|
||||
</div>
|
||||
)}
|
||||
<div className='mt-3 mb-3 bg-hover cursor-pointer' onClick={() => setShowSidebar(!showSidebar)}>
|
||||
{isXlViewport ? (
|
||||
<span className='icon-xl align-middle'>
|
||||
{showSidebar ? 'keyboard_double_arrow_left' : 'keyboard_double_arrow_right'}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span className='icon-xl align-middle'>
|
||||
{showSidebar ? 'keyboard_double_arrow_up' : 'keyboard_double_arrow_down'}
|
||||
</span>
|
||||
<span className='text-sm italic ml-1'>{showSidebar ? 'Hide filters' : 'Show filters'}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className='basis-3/4 grow flex flex-col xl:ml-1 bg-primary'>
|
||||
<DiariesTable />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
106
os-league-tools-master/src/pages/Faq.js
Normal file
106
os-league-tools-master/src/pages/Faq.js
Normal file
@@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import Card from '../components/common/Card';
|
||||
import Separator from '../components/common/Separator';
|
||||
import PageWrapper from '../components/PageWrapper';
|
||||
|
||||
export default function Faq() {
|
||||
const emphasisedText = 'text-accent font-semibold';
|
||||
return (
|
||||
<PageWrapper>
|
||||
<div className='container lg:max-w-[768px] mx-auto'>
|
||||
<Card>
|
||||
<Card.Header className=''>
|
||||
<p className='text-accent font-bold text-center small-caps text-2xl tracking-widest'>
|
||||
FREQUENTLY ASKED QUESTIONS
|
||||
</p>
|
||||
<p className='text-accent font-semibold text-center'>
|
||||
If your question is missing, come ask us in <Link text='discord' href='https://discord.gg/GQ5kVyU' />!
|
||||
</p>
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<Separator />
|
||||
<Header>How do I use the plugin on OpenOSRS / some other client?</Header>
|
||||
<Paragraph>
|
||||
OS League Tools <span className={emphasisedText}>does not</span> officially support any clients other than
|
||||
RuneLite.
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
If the plugin is broken on the client you are using, it is most likely caused by an out-of-date version.
|
||||
Generally this will be synced within a day or two, but it is outside my control. For the most stable
|
||||
experience, use the official RuneLite client only.
|
||||
</Paragraph>
|
||||
<Separator />
|
||||
<Header>How do I import tasks from the plugin to the site?</Header>
|
||||
<Paragraph>
|
||||
<ol>
|
||||
<li>
|
||||
1. Click the sidebar icon to open the Tasks Tracker plugin panel. Make sure "Leagues IV: Trailblazer
|
||||
Reloaded" is selected on the dropdown menu.
|
||||
</li>
|
||||
<li>
|
||||
2. Click the "Export" button at the bottom of the panel. Your task data will be automatically copied
|
||||
to your clipboard.
|
||||
</li>
|
||||
<li>
|
||||
3. Open https://www.osleague.tools and go to Manage Data -{'>'} Import. Paste your data into the text
|
||||
box and click "Sync".
|
||||
</li>
|
||||
</ol>
|
||||
</Paragraph>
|
||||
<Separator />
|
||||
<Header>How do I export my to-do list to the plugin?</Header>
|
||||
<Paragraph>
|
||||
<ol>
|
||||
<li>
|
||||
1. Open https://www.osleague.tools and go to Manage Data -{'>'} Export. Click on the text box to copy
|
||||
your data to the clipboard.
|
||||
</li>
|
||||
<li>
|
||||
2. On Runelite, click the sidebar icon to open the Tasks Tracker plugin panel. Make sure "Leagues IV:
|
||||
Trailblazer Reloaded" is selected on the dropdown menu.
|
||||
</li>
|
||||
<li>3. Click the "Import" button at the bottom of the panel and paste into the text box.</li>
|
||||
</ol>
|
||||
</Paragraph>
|
||||
<Separator />
|
||||
<Header>Importing tasks isn't working / Tasks aren't showing complete when they should</Header>
|
||||
<Paragraph>
|
||||
First, double check that you have the latest plugin version. You can see this by searching for{' '}
|
||||
<span className={emphasisedText}>Task Tracker</span> in the plugin hub. If it has an option to update,
|
||||
click it and retry your export.
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
As a last resort, you can also try:
|
||||
<ol>
|
||||
<li>1. Rebooting the runelite client and/or reinstalling the plugin</li>
|
||||
<li>2. Clear your browser data on the website to start fresh</li>
|
||||
</ol>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
If none of that helped, send a bug report (use the Feedback option in the top right menu) with as much
|
||||
information as you can, or come by the <Link text='discord' href='https://discord.gg/GQ5kVyU' /> to ask
|
||||
for help.
|
||||
</Paragraph>
|
||||
<Separator />
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</div>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
function Header({ children }) {
|
||||
return <p className='text-accent font-semibold text-center tracking-widest m-2'>{children}</p>;
|
||||
}
|
||||
|
||||
function Link({ text, href }) {
|
||||
return (
|
||||
<a href={href} target='_blank' rel='noreferrer' className='hover:underline text-accent font-semibold'>
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function Paragraph({ children }) {
|
||||
return <p className='indent-8 m-2 text-sm'>{children}</p>;
|
||||
}
|
||||
39
os-league-tools-master/src/pages/Homepage.js
Normal file
39
os-league-tools-master/src/pages/Homepage.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import React, { useState } from 'react';
|
||||
import newsPosts from '../data/newsPosts.json';
|
||||
import NewsCard from '../components/NewsCard';
|
||||
import PageWrapper from '../components/PageWrapper';
|
||||
import IconLinkCard from '../components/IconLinkCard';
|
||||
import LeagueCountdown from '../components/LeagueCountdown';
|
||||
import FeedbackModal from '../components/FeedbackModal';
|
||||
import ManageDataModal from '../components/ManageDataModal';
|
||||
import images from '../assets/images';
|
||||
|
||||
export default function Homepage() {
|
||||
const [isFeedbackModalOpen, setFeedbackModalOpen] = useState(false);
|
||||
const [isPluginModalOpen, setPluginModalOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<div className='md:flex md:flex-row justify-center'>
|
||||
<IconLinkCard title='Discord' href='https://discord.gg/GQ5kVyU' target='_blank' />
|
||||
<IconLinkCard title='Plugin' iconSrc={images['runelite-icon.svg']} onClick={() => setPluginModalOpen(true)} />
|
||||
<LeagueCountdown />
|
||||
<IconLinkCard title='Feedback' iconText='pest_control' onClick={() => setFeedbackModalOpen(true)} />
|
||||
<IconLinkCard title='About' iconText='help_outline' href='/about' />
|
||||
</div>
|
||||
<FeedbackModal isOpen={isFeedbackModalOpen} setIsOpen={val => setFeedbackModalOpen(val)} />
|
||||
<ManageDataModal variant='plugin' isOpen={isPluginModalOpen} setIsOpen={val => setPluginModalOpen(val)} />
|
||||
<p className='text-3xl small-caps ml-1 mt-2'>Updates</p>
|
||||
{newsPosts.map(newsPost => (
|
||||
<NewsCard
|
||||
key={newsPost.title}
|
||||
title={newsPost.title}
|
||||
date={newsPost.date}
|
||||
coverImg={newsPost.thumbnail}
|
||||
leadText={newsPost.leadText}
|
||||
htmlContent={newsPost.htmlContent}
|
||||
/>
|
||||
))}
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
43
os-league-tools-master/src/pages/QuestsPanel.js
Normal file
43
os-league-tools-master/src/pages/QuestsPanel.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React, { useState } from 'react';
|
||||
import QuestFilters from '../components/QuestFilters';
|
||||
import QuestTable from '../components/QuestTable';
|
||||
import useBreakpoint, { MEDIA_QUERIES, MODE } from '../hooks/useBreakpoint';
|
||||
|
||||
export default function QuestsPanel() {
|
||||
const isSmViewport = useBreakpoint(MEDIA_QUERIES.SM, MODE.LESS_OR_EQ);
|
||||
const isXlViewport = useBreakpoint(MEDIA_QUERIES.XL);
|
||||
const [showSidebar, setShowSidebar] = useState(isXlViewport);
|
||||
|
||||
return (
|
||||
<div className='flex flex-col xl:flex-row w-full bg-secondary-alt xl:bg-primary'>
|
||||
{isSmViewport && showSidebar && (
|
||||
<div className='mt-3 bg-hover cursor-pointer' onClick={() => setShowSidebar(!showSidebar)}>
|
||||
<span className='icon-xl align-middle'>keyboard_double_arrow_up</span>
|
||||
<span className='text-sm italic ml-1'>Hide filters</span>
|
||||
</div>
|
||||
)}
|
||||
{showSidebar && (
|
||||
<div className='basis-[23%] p-2'>
|
||||
<QuestFilters />
|
||||
</div>
|
||||
)}
|
||||
<div className='mt-3 mb-3 bg-hover cursor-pointer' onClick={() => setShowSidebar(!showSidebar)}>
|
||||
{isXlViewport ? (
|
||||
<span className='icon-xl align-middle'>
|
||||
{showSidebar ? 'keyboard_double_arrow_left' : 'keyboard_double_arrow_right'}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span className='icon-xl align-middle'>
|
||||
{showSidebar ? 'keyboard_double_arrow_up' : 'keyboard_double_arrow_down'}
|
||||
</span>
|
||||
<span className='text-sm italic ml-1'>{showSidebar ? 'Hide filters' : 'Show filters'}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className='basis-3/4 grow flex flex-col xl:ml-1 bg-primary'>
|
||||
<QuestTable />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
156
os-league-tools-master/src/pages/Settings.js
Normal file
156
os-league-tools-master/src/pages/Settings.js
Normal file
@@ -0,0 +1,156 @@
|
||||
import React from 'react';
|
||||
import { useSelector, useDispatch, batch } from 'react-redux';
|
||||
import { Tooltip as ReactTooltip } from 'react-tooltip';
|
||||
import { update } from '../store/settings/settings';
|
||||
import LabeledCheckbox from '../components/common/LabeledCheckbox';
|
||||
import TabbedCard from '../components/common/TabbedCard';
|
||||
import PageWrapper from '../components/PageWrapper';
|
||||
import images from '../assets/images';
|
||||
|
||||
export default function Settings() {
|
||||
const settingsState = useSelector(state => state.settings);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<div className='mx-auto'>
|
||||
<TabbedCard defaultActiveTab='interface'>
|
||||
<TabbedCard.Tab id='interface' label='Interface'>
|
||||
<div className='grid xl:grid-cols-2'>
|
||||
<div>
|
||||
<span className='heading-block-md small-caps mb-2 text-accent'>
|
||||
General{' '}
|
||||
<span className='icon-outline text-xs inline mr-1' data-tip data-for='general'>
|
||||
info
|
||||
</span>
|
||||
</span>
|
||||
<ReactTooltip id='general'>
|
||||
<p className='text-sm italic'>
|
||||
On large screens, site content will be limited to a maximum of 1500px.
|
||||
</p>
|
||||
<p className='text-sm italic'>Uncheck if you wish to use the full width of your browser window.</p>
|
||||
</ReactTooltip>
|
||||
<div className='ml-2'>
|
||||
<LabeledCheckbox
|
||||
label='Limit maximum content width'
|
||||
checked={settingsState.limitContentWidth}
|
||||
onClick={e => dispatch(update({ field: 'limitContentWidth', value: e.target.checked }))}
|
||||
/>
|
||||
</div>
|
||||
<span className='heading-block-md small-caps mb-2 mt-2 text-accent'>
|
||||
Task tracker{' '}
|
||||
<span className='icon-outline text-xs inline mr-1' data-tip data-for='taskTracker'>
|
||||
info
|
||||
</span>
|
||||
</span>
|
||||
<ReactTooltip id='taskTracker'>
|
||||
<p className='text-sm italic'>Choose which columns to show on the task tracker.</p>
|
||||
<p className='text-sm italic'>
|
||||
Note that on small screens, some columns may be hidden regardless of this setting.
|
||||
</p>
|
||||
</ReactTooltip>
|
||||
<div className='ml-2'>
|
||||
<LabeledCheckbox
|
||||
label='Show "Priority" column'
|
||||
checked={settingsState.taskColumns.priority}
|
||||
onClick={e =>
|
||||
dispatch(update({ field: 'taskColumns', subfield: 'priority', value: e.target.checked }))
|
||||
}
|
||||
/>
|
||||
<LabeledCheckbox
|
||||
label='Show "Category" column'
|
||||
checked={settingsState.taskColumns.category}
|
||||
onClick={e =>
|
||||
dispatch(update({ field: 'taskColumns', subfield: 'category', value: e.target.checked }))
|
||||
}
|
||||
/>
|
||||
<LabeledCheckbox
|
||||
label='Show "Completed At" column'
|
||||
checked={settingsState.taskColumns.completedAt}
|
||||
onClick={e =>
|
||||
dispatch(update({ field: 'taskColumns', subfield: 'completedAt', value: e.target.checked }))
|
||||
}
|
||||
/>
|
||||
<LabeledCheckbox
|
||||
label='Show "Regions" column'
|
||||
checked={settingsState.taskColumns.regions}
|
||||
onClick={e =>
|
||||
dispatch(update({ field: 'taskColumns', subfield: 'regions', value: e.target.checked }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className='heading-block-md small-caps my-2 text-accent'>Mode</span>
|
||||
<div className='ml-2 mb-4 flex flex-row flex-wrap gap-4'>
|
||||
<ModeSelectCard label='Dark' mode='dark' />
|
||||
<ModeSelectCard label='Light' mode='light' />
|
||||
</div>
|
||||
<span className='heading-block-md small-caps my-2 text-accent'>Theme</span>
|
||||
<div className='ml-2 flex flex-row flex-wrap gap-4'>
|
||||
<ThemeSelectCard label='Twisted' theme='tl' />
|
||||
<ThemeSelectCard label='Trailblazer' theme='tb' />
|
||||
<ThemeSelectCard label='Shattered' theme='sl' />
|
||||
<ThemeSelectCard label='Reloaded' theme='tr' />
|
||||
<ThemeSelectCard label='Echoes' theme='re' />
|
||||
<ThemeSelectCard label='Mono' theme='mono' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabbedCard.Tab>
|
||||
</TabbedCard>
|
||||
</div>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
function ModeSelectCard({ label, mode }) {
|
||||
const activeTheme = useSelector(state => state.settings.theme);
|
||||
const activeMode = useSelector(state => state.settings.mode);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const themeMode = `${activeTheme.split('-')[0]}-${mode}`;
|
||||
const selected = activeMode === mode;
|
||||
const selectedStyle = selected ? 'border-x-2 border-accent bg-secondary-alt' : 'cursor-pointer bg-hover';
|
||||
return (
|
||||
<div
|
||||
className={`rounded p-2 w-[100px] min-w-[100px] ${selectedStyle}`}
|
||||
onClick={() =>
|
||||
batch(() => {
|
||||
dispatch(update({ field: 'theme', value: themeMode }));
|
||||
dispatch(update({ field: 'mode', value: mode }));
|
||||
})
|
||||
}
|
||||
>
|
||||
<img className='h-9 w-9 mx-auto' src={images[`icon-blank-${mode}.png`]} alt='' />
|
||||
<span className={`text-center heading-block-sm pt-2 small-caps force-wrap ${selected && 'text-accent'}`}>
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ThemeSelectCard({ label, theme }) {
|
||||
const activeTheme = useSelector(state => state.settings.theme);
|
||||
const activeMode = useSelector(state => state.settings.mode);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const themeMode = `${theme}-${activeMode}`;
|
||||
const selected = activeTheme === themeMode;
|
||||
const selectedStyle = selected ? 'border-x-2 border-accent bg-secondary-alt' : 'cursor-pointer bg-hover';
|
||||
return (
|
||||
<div
|
||||
className={`rounded p-2 my-auto w-[100px] min-w-[100px] ${selectedStyle}`}
|
||||
onClick={() =>
|
||||
batch(() => {
|
||||
dispatch(update({ field: 'theme', value: themeMode }));
|
||||
})
|
||||
}
|
||||
>
|
||||
<img className='h-9 w-9 mx-auto' src={images[`icon-${theme}-split.png`]} alt='' />
|
||||
<span className={`text-center heading-block-sm pt-2 small-caps force-wrap ${selected && 'text-accent'}`}>
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
198
os-league-tools-master/src/pages/Statistics.js
Normal file
198
os-league-tools-master/src/pages/Statistics.js
Normal file
@@ -0,0 +1,198 @@
|
||||
import { forEach } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Chart } from 'react-charts';
|
||||
import { useSelector } from 'react-redux';
|
||||
import Card from '../components/common/Card';
|
||||
import PageWrapper from '../components/PageWrapper';
|
||||
import { LEAGUE_END_DATE, LEAGUE_START_DATE } from '../data/constants';
|
||||
import tasks from '../data/tasks';
|
||||
import getAccentColorForTheme from '../util/colors';
|
||||
|
||||
const DATE_FORMAT = 'en-US';
|
||||
const DATE_OPTIONS = { month: 'short', day: 'numeric' };
|
||||
|
||||
function createLeagueStats(dataByCompletionDate) {
|
||||
const nextDate = new Date(LEAGUE_START_DATE.getTime());
|
||||
nextDate.setHours(0);
|
||||
const taskCounts = [];
|
||||
const pointCounts = [];
|
||||
while (nextDate <= LEAGUE_END_DATE) {
|
||||
taskCounts.push({
|
||||
date: new Date(nextDate.getTime()).toLocaleDateString(DATE_FORMAT, DATE_OPTIONS),
|
||||
count: dataByCompletionDate[nextDate]?.tasksComplete ?? 0,
|
||||
secondaryAxisId: 'daily',
|
||||
});
|
||||
pointCounts.push({
|
||||
date: new Date(nextDate.getTime()).toLocaleDateString(DATE_FORMAT, DATE_OPTIONS),
|
||||
count: dataByCompletionDate[nextDate]?.pointsEarned ?? 0,
|
||||
secondaryAxisId: 'daily',
|
||||
});
|
||||
nextDate.setDate(nextDate.getDate() + 1);
|
||||
}
|
||||
|
||||
const cumulativeTaskCounts = [];
|
||||
const cumulativePointCounts = [];
|
||||
let cumulativeTasks = 0;
|
||||
let cumulativePoints = 0;
|
||||
for (let i = 0; i < taskCounts.length; i++) {
|
||||
cumulativeTasks += taskCounts[i].count;
|
||||
cumulativePoints += pointCounts[i].count;
|
||||
cumulativeTaskCounts.push({
|
||||
date: taskCounts[i].date,
|
||||
count: cumulativeTasks,
|
||||
});
|
||||
cumulativePointCounts.push({
|
||||
date: taskCounts[i].date,
|
||||
count: cumulativePoints,
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: 'Tasks completed (daily)',
|
||||
data: taskCounts,
|
||||
elementType: 'bar',
|
||||
},
|
||||
{
|
||||
label: 'Tasks completed (total)',
|
||||
data: cumulativeTaskCounts,
|
||||
},
|
||||
{
|
||||
label: 'Points earned (daily)',
|
||||
data: pointCounts,
|
||||
elementType: 'bar',
|
||||
},
|
||||
{
|
||||
label: 'Points earned (total)',
|
||||
data: cumulativePointCounts,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function createDataByCompletionDate(taskState) {
|
||||
const dataByCompletionDate = {};
|
||||
forEach(Object.keys(taskState.tasks), taskKey => {
|
||||
if (taskState.tasks[taskKey].completed) {
|
||||
const completedDate = new Date(0);
|
||||
completedDate.setUTCSeconds(taskState.tasks[taskKey].completed / 1000);
|
||||
completedDate.setHours(0);
|
||||
completedDate.setMinutes(0);
|
||||
completedDate.setSeconds(0);
|
||||
if (dataByCompletionDate[completedDate]?.tasksComplete) {
|
||||
dataByCompletionDate[completedDate].tasksComplete += 1;
|
||||
} else if (dataByCompletionDate[completedDate]) {
|
||||
dataByCompletionDate[completedDate].tasksComplete = 1;
|
||||
} else {
|
||||
dataByCompletionDate[completedDate] = {};
|
||||
dataByCompletionDate[completedDate].tasksComplete = 1;
|
||||
}
|
||||
|
||||
const pointValue = tasks[taskKey]?.difficulty.value;
|
||||
if (dataByCompletionDate[completedDate]?.pointsEarned) {
|
||||
dataByCompletionDate[completedDate].pointsEarned += pointValue;
|
||||
} else if (dataByCompletionDate[completedDate]) {
|
||||
dataByCompletionDate[completedDate].pointsEarned = pointValue;
|
||||
} else {
|
||||
dataByCompletionDate[completedDate] = {};
|
||||
dataByCompletionDate[completedDate].pointsEarned = pointValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
return dataByCompletionDate;
|
||||
}
|
||||
|
||||
export default function Statistics() {
|
||||
const taskState = useSelector(state => state.tasks);
|
||||
const theme = useSelector(state => state.settings.theme);
|
||||
const leagueStats = useMemo(() => createLeagueStats(createDataByCompletionDate(taskState)), [taskState]);
|
||||
|
||||
const primaryAxis = useMemo(
|
||||
() => ({
|
||||
getValue: datum => datum.date,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const secondaryAxes = useMemo(
|
||||
() => [
|
||||
{
|
||||
getValue: datum => datum.count,
|
||||
elementType: 'line',
|
||||
},
|
||||
{
|
||||
getValue: datum => datum.count,
|
||||
elementType: 'bar',
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<div className='container h-[800px] mx-auto'>
|
||||
<Card className='h-full'>
|
||||
<Card.Body>
|
||||
<div className='grid grid-cols-2 gap-4 h-full'>
|
||||
<div className='h-[90%] pb-3'>
|
||||
<h1 className='heading-accent-md'>Tasks completed per day</h1>
|
||||
<Chart
|
||||
options={{
|
||||
data: [leagueStats[0]],
|
||||
primaryAxis,
|
||||
secondaryAxes: [secondaryAxes[1]],
|
||||
getSeriesStyle: () => ({
|
||||
color: getAccentColorForTheme(theme),
|
||||
}),
|
||||
dark: theme.split('-')[1] === 'dark',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className='h-[90%] pb-3'>
|
||||
<h1 className='heading-accent-md'>Points earned per day</h1>
|
||||
<Chart
|
||||
options={{
|
||||
data: [leagueStats[2]],
|
||||
primaryAxis,
|
||||
secondaryAxes: [secondaryAxes[1]],
|
||||
getSeriesStyle: () => ({
|
||||
color: getAccentColorForTheme(theme),
|
||||
}),
|
||||
dark: theme.split('-')[1] === 'dark',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className='h-[90%] pb-3'>
|
||||
<h1 className='heading-accent-md'>Cumulative tasks completed</h1>
|
||||
<Chart
|
||||
options={{
|
||||
data: [leagueStats[1]],
|
||||
primaryAxis,
|
||||
secondaryAxes: [secondaryAxes[0]],
|
||||
getSeriesStyle: () => ({
|
||||
color: getAccentColorForTheme(theme),
|
||||
}),
|
||||
dark: theme.split('-')[1] === 'dark',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className='h-[90%] pb-3'>
|
||||
<h1 className='heading-accent-md'>Cumulative points earned</h1>
|
||||
<Chart
|
||||
options={{
|
||||
data: [leagueStats[3]],
|
||||
primaryAxis,
|
||||
secondaryAxes: [secondaryAxes[0]],
|
||||
getSeriesStyle: () => ({
|
||||
color: getAccentColorForTheme(theme),
|
||||
}),
|
||||
dark: theme.split('-')[1] === 'dark',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</div>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
112
os-league-tools-master/src/pages/TasksPanel.js
Normal file
112
os-league-tools-master/src/pages/TasksPanel.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { ThemedProgressBar } from '../components/ThemeProvider';
|
||||
import Separator from '../components/common/Separator';
|
||||
import TaskFilters from '../components/TaskFilters';
|
||||
import TaskGenerator from '../components/TaskGenerator';
|
||||
import TaskTable from '../components/TaskTable';
|
||||
import useBreakpoint, { MEDIA_QUERIES, MODE } from '../hooks/useBreakpoint';
|
||||
import { RELIC_UNLOCK_THRESHOLDS } from '../data/relics';
|
||||
import useTrackerHistory from '../hooks/useTrackerHistory';
|
||||
import { getRegionTier } from '../util/getTier';
|
||||
import { REGION_UNLOCK_THRESHOLDS } from '../data/regions';
|
||||
|
||||
export default function TasksPanel({ readonly, taskState }) {
|
||||
const isSmViewport = useBreakpoint(MEDIA_QUERIES.SM, MODE.LESS_OR_EQ);
|
||||
const isXlViewport = useBreakpoint(MEDIA_QUERIES.XL);
|
||||
const [showSidebar, setShowSidebar] = useState(isXlViewport);
|
||||
const { taskStats, tier } = useSelector(state => state.tasks);
|
||||
const regionTier = getRegionTier(taskStats.tasks.complete.total);
|
||||
const history = useTrackerHistory();
|
||||
|
||||
return (
|
||||
<div className='h-full'>
|
||||
<div className='mb-3'>
|
||||
<div className='flex flex-wrap text-accent font-semibold justify-evenly gap-2'>
|
||||
<span>
|
||||
Tasks: {taskStats.tasks.complete.total} / {taskStats.tasks.available.total}
|
||||
</span>
|
||||
<span>
|
||||
Points: {taskStats.points.complete.total} / {taskStats.points.available.total}
|
||||
</span>
|
||||
<span>
|
||||
To-do: {taskStats.tasks.todo.total} tasks ({taskStats.points.todo.total} points)
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex flex-row w-full gap-5 mt-3'>
|
||||
<div className='basis-1/2'>
|
||||
<div className='mb-1'>
|
||||
<ThemedProgressBar
|
||||
curValue={taskStats.points.complete.total}
|
||||
maxValue={RELIC_UNLOCK_THRESHOLDS[RELIC_UNLOCK_THRESHOLDS.length - 1]}
|
||||
steps={RELIC_UNLOCK_THRESHOLDS}
|
||||
/>
|
||||
</div>
|
||||
<div className='text-accent text-sm text-center'>
|
||||
{tier < RELIC_UNLOCK_THRESHOLDS.length ? (
|
||||
<span>{`Next relic unlocked at ${RELIC_UNLOCK_THRESHOLDS[tier]} pts (${
|
||||
RELIC_UNLOCK_THRESHOLDS[tier] - taskStats.points.complete.total
|
||||
} remaining)`}</span>
|
||||
) : (
|
||||
'All relics unlocked'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='basis-1/2'>
|
||||
<div className='shadow-subdued mb-1'>
|
||||
<ThemedProgressBar
|
||||
curValue={taskStats.tasks.complete.total}
|
||||
maxValue={REGION_UNLOCK_THRESHOLDS[REGION_UNLOCK_THRESHOLDS.length - 1]}
|
||||
steps={REGION_UNLOCK_THRESHOLDS}
|
||||
/>
|
||||
</div>
|
||||
<div className='text-accent text-sm text-center'>
|
||||
{regionTier < REGION_UNLOCK_THRESHOLDS.length - 1 ? (
|
||||
<span>{`Next region unlocked at ${REGION_UNLOCK_THRESHOLDS[regionTier + 1]} tasks (${
|
||||
REGION_UNLOCK_THRESHOLDS[regionTier + 1] - taskStats.tasks.complete.total
|
||||
} remaining)`}</span>
|
||||
) : (
|
||||
'All regions unlocked'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className='flex xl:flex-row flex-col justify-around w-full bg-secondary-alt xl:bg-primary'>
|
||||
{isSmViewport && showSidebar && (
|
||||
<div className='mt-3 bg-hover cursor-pointer' onClick={() => setShowSidebar(!showSidebar)}>
|
||||
<span className='icon-xl align-middle'>keyboard_double_arrow_up</span>
|
||||
<span className='text-sm italic ml-1'>Hide filters</span>
|
||||
</div>
|
||||
)}
|
||||
{showSidebar && (
|
||||
<div className='basis-[23%] flex flex-col gap-3 pl-2'>
|
||||
<TaskFilters history={history} />
|
||||
<Separator />
|
||||
{!readonly && <TaskGenerator />}
|
||||
</div>
|
||||
)}
|
||||
<div className='mt-3 mb-3 bg-hover cursor-pointer' onClick={() => setShowSidebar(!showSidebar)}>
|
||||
{isXlViewport ? (
|
||||
<span className='icon-xl align-middle'>
|
||||
{showSidebar ? 'keyboard_double_arrow_left' : 'keyboard_double_arrow_right'}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span className='icon-xl align-middle'>
|
||||
{showSidebar ? 'keyboard_double_arrow_up' : 'keyboard_double_arrow_down'}
|
||||
</span>
|
||||
<span className='text-sm italic ml-1'>{showSidebar ? 'Hide filters' : 'Show filters'}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className='basis-3/4 grow flex flex-col xl:ml-1 bg-primary'>
|
||||
<div className='border-t xl:border-l xl:border-t-0 pt-2 xl:pt-[0] border-subdued grow xl:mt-3'>
|
||||
<TaskTable history={history} readonly={readonly} taskState={taskState} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
48
os-league-tools-master/src/pages/Tracker.js
Normal file
48
os-league-tools-master/src/pages/Tracker.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import CharacterPanel from './CharacterPanel';
|
||||
import TabbedCard from '../components/common/TabbedCard';
|
||||
import PageWrapper from '../components/PageWrapper';
|
||||
import TasksPanel from './TasksPanel';
|
||||
import useQueryString from '../hooks/useQueryString';
|
||||
import QuestsPanel from './QuestsPanel';
|
||||
import images from '../assets/images';
|
||||
import DiariesPanel from './DiariesPanel';
|
||||
import useBreakpoint, { MEDIA_QUERIES, MODE } from '../hooks/useBreakpoint';
|
||||
import useFetchHiscoresOnLoad from '../hooks/useFetchHiscoresOnLoad';
|
||||
|
||||
export default function Tracker() {
|
||||
const [selectedTab, onSetSelectedTab] = useQueryString('tab');
|
||||
const isXsOrSmallerViewport = useBreakpoint(MEDIA_QUERIES.XS, MODE.LESS_OR_EQ);
|
||||
useFetchHiscoresOnLoad();
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<TabbedCard defaultActiveTab={selectedTab} setTabCallback={onSetSelectedTab}>
|
||||
<TabbedCard.Tab
|
||||
id='character'
|
||||
label={isXsOrSmallerViewport ? undefined : 'Character'}
|
||||
icon={images['tab-character.png']}
|
||||
>
|
||||
<CharacterPanel />
|
||||
</TabbedCard.Tab>
|
||||
<TabbedCard.Tab id='tasks' label={isXsOrSmallerViewport ? undefined : 'Tasks'} icon={images['tab-tasks.png']}>
|
||||
<TasksPanel />
|
||||
</TabbedCard.Tab>
|
||||
<TabbedCard.Tab
|
||||
id='quests'
|
||||
label={isXsOrSmallerViewport ? undefined : 'Quests'}
|
||||
icon={images['tab-quests.png']}
|
||||
>
|
||||
<QuestsPanel />
|
||||
</TabbedCard.Tab>
|
||||
<TabbedCard.Tab
|
||||
id='diaries'
|
||||
label={isXsOrSmallerViewport ? undefined : 'Diaries'}
|
||||
icon={images['tab-diaries.png']}
|
||||
>
|
||||
<DiariesPanel />
|
||||
</TabbedCard.Tab>
|
||||
</TabbedCard>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
45
os-league-tools-master/src/pages/ViewCharacter.js
Normal file
45
os-league-tools-master/src/pages/ViewCharacter.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import PageWrapper from '../components/PageWrapper';
|
||||
import TasksPanel from './TasksPanel';
|
||||
|
||||
import Card from '../components/common/Card';
|
||||
import Banner from '../components/common/Banner';
|
||||
import { getUserByRsn } from '../client/user-data-client';
|
||||
import Spinner from '../components/common/Spinner';
|
||||
|
||||
export default function ViewCharacter() {
|
||||
const { character } = useParams();
|
||||
const [taskState, setTaskState] = useState();
|
||||
const [error, setError] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
getUserByRsn(character).then(res => {
|
||||
if (res.success) {
|
||||
setTaskState(JSON.parse(res.value[`tasks_${character}`].S));
|
||||
} else {
|
||||
setError('Unable to load tasks for character.');
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<Banner className='mb-4 text-center'>
|
||||
Viewing tasks for character <span className='heading-accent-md'>{character}</span>
|
||||
</Banner>
|
||||
<Card>
|
||||
<Card.Body>
|
||||
{taskState ? (
|
||||
<TasksPanel readonly taskState={taskState} />
|
||||
) : (
|
||||
<div className='flex flex-col gap-4 items-center justify-center w-full text-center'>
|
||||
<Spinner />
|
||||
{error && <span className='text-error'>{error}</span>}
|
||||
</div>
|
||||
)}
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user