Init commit

This commit is contained in:
Blossomi Shymae
2024-05-01 20:38:02 -05:00
commit 75bc1827e4
29 changed files with 11845 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "public/lib/MaterialDesign"]
path = public/lib/MaterialDesign
url = git@github.com:Templarian/MaterialDesign.git

73
README.md Normal file
View File

@@ -0,0 +1,73 @@
# Clean Cuts
![](preview.png)
## Setup
Make sure to install the dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm run dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm run build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm run preview
# yarn
yarn preview
# bun
bun run preview
```

27
app.vue Normal file
View File

@@ -0,0 +1,27 @@
<template>
<NuxtLayout>
<NuxtPage keepalive />
</NuxtLayout>
</template>
<style>
.page-enter-active,
.page-leave-active {
transition: all 0.2s;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
filter: blur(0.125rem);
}
.layout-enter-active,
.layout-leave-active {
transform: none;
transition: all 0.5s;
}
.layout-enter-from,
.layout-leave-to {
transform: translateX(-100%);
}
</style>

18
components/Badge.vue Normal file
View File

@@ -0,0 +1,18 @@
<template>
<span class="badge text-bg-dark border text-dark-emphasis fw-normal d-flex align-items-center gap-2 bg-transparent bg-blur-4 border-light border-opacity-25">
<MaterialIcon :size="24" :name="name" />
<slot></slot>
</span>
</template>
<script setup lang="ts">
import MaterialIcon from './MaterialIcon.vue';
defineProps<{
name: string
}>();
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,19 @@
<template>
<svg :width="size" :height="size">
<image :href="`/lib/MaterialDesign/svg/${name}.svg`"
:width="size"
:height="size"/>
</svg>
</template>
<script setup lang="ts">
defineProps<{
name: string,
size: number
}>();
</script>
<style lang="scss" scoped>
</style>

41
components/Pagination.vue Normal file
View File

@@ -0,0 +1,41 @@
<template>
<div class="btn-group">
<NuxtLink :class="`btn btn-outline-dark ${hasFurtherPreviousCss}`">
<MaterialIcon name="chevron-double-left" :size="32" />
</NuxtLink>
<NuxtLink :class="`btn btn-outline-dark ${hasPreviousCss}`">
<MaterialIcon name="chevron-left" :size="32" />
</NuxtLink>
<NuxtLink :class="`btn btn-outline-dark text-light`">
{{ pageIndex / totalPages }}
</NuxtLink>
<NuxtLink :class="`btn btn-outline-dark ${hasNextCss}`">
<MaterialIcon name="chevron-right" :size="32" />
</NuxtLink>
<NuxtLink :class="`btn btn-outline-dark ${hasFurtherNextCss}`">
<MaterialIcon name="chevron-double-right" :size="32" />
</NuxtLink>
</div>
</template>
<script setup lang="ts">
import MaterialIcon from './MaterialIcon.vue';
const props = defineProps<{
hasPrevious: boolean;
hasFurtherPrevious: boolean;
hasNext: boolean;
hasFurtherNext: boolean;
pageIndex: number;
totalPages: number;
}>();
const hasPreviousCss = !props.hasPrevious ? "disabled" : "";
const hasFurtherPreviousCss = !props.hasFurtherPrevious ? "disabled" : "";
const hasNextCss = !props.hasNext ? "disabled" : "";
const hasFurtherNextCss = !props.hasFurtherNext ? "disabled" : "";
</script>
<style lang="scss" scoped>
</style>

3
components/TheTitle.vue Normal file
View File

@@ -0,0 +1,3 @@
<template>
<span>Clean Cuts</span>
</template>

9
composables/useClient.ts Normal file
View File

@@ -0,0 +1,9 @@
import { Client } from "~/core/client";
const client = new Client();
export default function useClient(): { client: Client } {
return {
client
}
};

70
core/client.ts Normal file
View File

@@ -0,0 +1,70 @@
import { ChampionSummary, Item, LocaleVersionArgs, Perk, SummonerEmote, SummonerIcon, WardSkin } from "./models";
import axios from "axios";
export abstract class ApiObject {
static url = "https://raw.communitydragon.org";
getClientPath(args: LocaleVersionArgs): string {
return `${ApiObject.url}/${args.version}/plugins/rcp-be-lol-game-data/global/${args.locale}`;
}
}
export class Client {
public items: ItemApi;
public perks: PerkApi;
public championSummaries: ChampionSummaryApi;
public summonerEmotes: SummonerEmoteApi;
public summonerIcons: SummonerIconApi;
public wardSkins: WardSkinApi;
constructor() {
this.items = new ItemApi();
this.perks = new PerkApi();
this.championSummaries = new ChampionSummaryApi();
this.summonerEmotes = new SummonerEmoteApi();
this.summonerIcons = new SummonerIconApi();
this.wardSkins = new WardSkinApi();
}
}
export class ItemApi extends ApiObject {
async listAsync(args: LocaleVersionArgs): Promise<Array<Item>> {
let res = await axios.get(`${this.getClientPath(args)}/v1/items.json`);
return res.data.map((x: any) => new Item(x));
}
}
export class PerkApi extends ApiObject {
async listAsync(args: LocaleVersionArgs): Promise<Array<Perk>> {
let res = await axios.get(`${this.getClientPath(args)}/v1/perks.json`);
return res.data.map((x: any) => new Perk(x));
}
}
export class ChampionSummaryApi extends ApiObject {
async listAsync(args: LocaleVersionArgs): Promise<Array<ChampionSummary>> {
let res = await axios.get(`${this.getClientPath(args)}/v1/champion-summary.json`);
return res.data.map((x: any) => new ChampionSummary(x));
}
}
export class SummonerEmoteApi extends ApiObject {
async listAsync(args: LocaleVersionArgs): Promise<Array<SummonerEmote>> {
let res = await axios.get(`${this.getClientPath(args)}/v1/summoner-emotes.json`);
return res.data.map((x: any) => new SummonerEmote(x));
}
}
export class SummonerIconApi extends ApiObject {
async listAsync(args: LocaleVersionArgs): Promise<Array<SummonerIcon>> {
let res = await axios.get(`${this.getClientPath(args)}/v1/summoner-icons.json`);
return res.data.map((x: any) => new SummonerEmote(x));
}
}
export class WardSkinApi extends ApiObject {
async listAsync(args: LocaleVersionArgs): Promise<Array<WardSkin>> {
let res = await axios.get(`${this.getClientPath(args)}/v1/ward-skins.json`);
return res.data.map((x: any) => new WardSkin(x));
}
}

282
core/models.ts Normal file
View File

@@ -0,0 +1,282 @@
export class LocaleVersionArgs {
locale: string;
version: string
constructor({locale, version}: {locale: string, version: string}) {
this.locale = locale;
this.version = version;
}
}
export abstract class CommunityDragonObject {
static url = "https://raw.communitydragon.org";
resolveClientPath({path, args}: {path: string, args: LocaleVersionArgs}): string {
const uri = path.replace("/lol-game-data/assets", "").toLowerCase();
return `${CommunityDragonObject.url}/${args.version}/plugins/rcp-be-lol-game-data/global/${args.locale}/${uri}`;
}
resolveGamePath({path, version}: {path: string, version: string}): string {
const uri = path.replace("/lol-game-data/assets/ASSETS/", "").replace(".jpg", ".png").toLowerCase();
return `${CommunityDragonObject.url}/${version}/game/assets/${uri}`;
}
}
export class Champion extends CommunityDragonObject {
id: number;
name: string;
alias: string;
title: string;
shortBio: string;
playstyleInfo: PlaystyleInfo;
skins: Array<Skin>;
passive: Passive;
spells: Array<Spell>;
constructor(json: any) {
super();
this.id = json.id;
this.name = json.name;
this.alias = json.alias;
this.title = json.title;
this.shortBio = json.shortBio;
this.playstyleInfo = new PlaystyleInfo(json.playstyleInfo);
this.skins = json.skins.map((x: any) => new Skin(x));
this.passive = new Passive(json.passive);
this.spells = json.spells.map((x: any) => new Spell(x));
}
}
export class PlaystyleInfo extends CommunityDragonObject {
damage: number;
durability: number;
crowdControl: number;
mobility: number;
utility: number;
constructor(json: any) {
super();
this.damage = json.damage;
this.durability = json.durability;
this.crowdControl = json.crowdControl;
this.mobility = json.mobility;
this.utility = json.utility;
}
}
export class Skin extends CommunityDragonObject {
id: number;
isBase: boolean;
name: string;
rarity: string;
isLegacy: boolean;
loadScreenPath: string;
constructor(json: any) {
super();
this.id = json.id;
this.isBase = json.isBase;
this.name = json.name;
this.rarity = json.rarity;
this.isLegacy = json.isLegacy;
this.loadScreenPath = json.loadScreenPath;
}
getLoadScreen(version: string): string {
return this.resolveGamePath({path: this.loadScreenPath, version: version});
}
}
export class Passive extends CommunityDragonObject {
name: string;
description: string;
constructor(json: any) {
super();
this.name = json.name;
this.description = json.description;
}
}
export class Spell extends CommunityDragonObject {
spellKey: string;
name: string;
description: string;
constructor(json: any) {
super();
this.spellKey = json.spellKey;
this.name = json.name;
this.description = json.description;
}
}
export class ChampionSummary extends CommunityDragonObject {
id: number;
name: string;
alias: string;
squarePortraitPath: string;
constructor(json: any) {
super();
this.id = json.id;
this.name = json.name;
this.alias = json.alias;
this.squarePortraitPath = json.squarePortraitPath;
}
getIcon(args: LocaleVersionArgs): string {
return this.resolveClientPath({path: this.squarePortraitPath, args: args});
}
}
export class Item extends CommunityDragonObject {
id: number;
name: string;
description: string;
active: boolean;
inStore: boolean;
from: Array<number>;
to: Array<number>;
categories: Array<number>;
price: number;
priceTotal: number;
iconPath: string;
constructor(json: any) {
super();
this.id = json.id;
this.name = json.name;
this.description = json.description;
this.active = json.active;
this.inStore = json.inStore;
this.from = json.from;
this.to = json.to;
this.categories = json.categories;
this.price = json.price;
this.priceTotal = json.priceTotal;
this.iconPath = json.iconPath;
}
getIcon(version: string): string {
return this.resolveGamePath({path: this.iconPath, version: version});
}
}
export class Perk extends CommunityDragonObject {
id: number;
name: string;
shortDesc: string;
longDesc: string;
iconPath: string;
constructor(json: any) {
super();
this.id = json.id;
this.name = json.name;
this.shortDesc = json.shortDesc;
this.longDesc = json.longDesc;
this.iconPath = json.iconPath;
}
getIcon(version: string): string {
return this.resolveClientPath({path: this.iconPath, args: {version: version, locale: "default"}});
}
}
export class SummonerEmote extends CommunityDragonObject {
id: number;
name: string;
inventoryIcon: string;
constructor(json: any) {
super();
this.id = json.id;
this.name = json.name;
this.inventoryIcon = json.inventoryIcon;
}
getInventoryIcon(version: string): string {
return this.resolveGamePath({path: this.inventoryIcon, version: version}).replace("inventory", "vfx");
}
}
export class SummonerIcon extends CommunityDragonObject {
id: number;
title: string;
yearReleased: number;
isLegacy: boolean;
imagePath?: string;
descriptions: Array<Description>;
rarities: Array<Rarity>;
constructor(json: any) {
super();
this.id = json.id;
this.title = json.title;
this.yearReleased = json.yearReleased;
this.isLegacy = json.isLegacy;
this.imagePath = json.imagePath;
this.descriptions = json.descriptions.map((x: any) => new Description(x));
this.rarities = json.rarities.map((x: any) => new Rarity(x));
}
}
export class WardSkin extends CommunityDragonObject {
id: number;
name: string;
description : string;
wardImagePath: string;
wardShadowImagePath: string;
isLegacy: boolean;
regionDescriptions: Array<Description>;
rarities: Array<Rarity>;
constructor(json: any) {
super();
this.id = json.id;
this.name = json.name;
this.description = json.description;
this.wardImagePath = json.wardImagePath;
this.wardShadowImagePath = json.wardShadowImagePath;
this.isLegacy = json.isLegacy;
this.regionDescriptions = json.regionalDescriptions.map((x: any) => new Description(x));
this.rarities = json.rarities.map((x: any) => new Rarity(x));
}
}
export class Description extends CommunityDragonObject {
region: string;
description: string;
constructor(json: any) {
super();
this.region = json.region;
this.description = json.description;
}
}
export class Rarity extends CommunityDragonObject {
region: string;
rarity: string;
constructor(json: any) {
super();
this.region = json.region;
this.rarity = json.rarity;
}
}

128
layouts/default.vue Normal file
View File

@@ -0,0 +1,128 @@
<template>
<div class="h-100">
<header>
<div class="background background-transparent background-blur-2"></div>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light border-bottom border-light border-opacity-25 border-2 box-shadow">
<div class="container">
<NuxtLink class="navbar-brand fw-light" to="/" ><Title /></NuxtLink>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<NuxtLink class="nav-link" to="/">
<MaterialIcon name="home" :size="24" /> Home
</NuxtLink>
</li>
<li class="nav-item">
<NuxtLink class="nav-link" to="/">
<MaterialIcon name="account-group" :size="24" /> Champions
</NuxtLink>
</li>
<li class="nav-item">
<NuxtLink class="nav-link" to="/items">
<MaterialIcon name="magic-staff" :size="24" /> Items
</NuxtLink>
</li>
<li class="nav-item">
<NuxtLink class="nav-link" to="/">
<MaterialIcon name="shield" :size="24" /> Runes
</NuxtLink>
</li>
<li class="nav-item">
<NuxtLink class="nav-link" to="/">
<MaterialIcon name="image" :size="24" /> Summoner Icons
</NuxtLink>
</li>
<li class="nav-item">
<NuxtLink class="nav-link" to="/">
<MaterialIcon name="floor-lamp" :size="24" /> Ward Skins
</NuxtLink>
</li>
<li class="nav-item">
<NuxtLink class="nav-link" to="/">
<MaterialIcon name="face-woman-shimmer" :size="24" /> Emotes
</NuxtLink>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pt-3 pb-3">
<slot></slot>
</main>
</div>
<footer class="container border border-light border-opacity-25 rounded p-4 pb-2 mt-2 mb-4 bg-blur-4">
<div class="d-flex justify-content-around align-items-center gap-2 mb-3 flex-wrap">
<NuxtLink class="text-decoration-none text-light" to="about">About</NuxtLink>
<a class="text-decoration-none text-light" href="https://challenges.darkintaqt.com" referrerpolicy="no-referrer">Challenge Tracker</a>
<a class="text-decoration-none text-light" href="https://blossomishymae.github.io/" referrerpolicy="no-referrer">blossomishymae.github.io</a>
<a class="text-decoration-none text-light" href="https://communitydragon.org" referrerpolicy="no-referrer">CommunityDragon</a>
<!-- <a class="text-decoration-none text-light" href="https://discord.com/invite/riotgamesdevrel" referrerpolicy="no-referrer">DevRel Discord</a> -->
</div>
<div class="d-flex justify-content-center align-items-center pt-2 border-top border-light border-opacity-25">
<p class="text-muted fw-light" style="font-size: 0.8rem;">Peaches was created under Riot Games' "Legal Jibber Jabber" policy using assets owned by Riot Games. Riot Games does not endorse or sponsor this project. </p>
</div>
</footer>
</div>
</template>
<script>
import MaterialIcon from '~/components/MaterialIcon.vue';
import Title from '~/components/Title.vue';
</script>
<style lang="scss">
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
a {
color: #0077cc;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px;
}
</style>

62
nuxt.config.ts Normal file
View File

@@ -0,0 +1,62 @@
import path from "path";
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
nitro: {
output: {
publicDir: path.join(__dirname, "docs"),
},
},
app: {
pageTransition: {
name: "page",
mode: "out-in",
},
layoutTransition: {
name: "layout",
mode: "out-in",
},
head: {
htmlAttrs: {
"data-bs-theme": "dark",
"style": "background-image: url('https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-splashes/498/498004.jpg');"
},
bodyAttrs: {
class: "h-100"
},
meta: [
{ name: "viewport", content: "width=device-width, initial-scale=1" },
],
link: [
{
rel: "stylesheet",
href: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css",
integrity:
"sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM",
crossorigin: "anonymous",
},
{
rel: "stylesheet",
href: "/css/app.css",
},
{
rel: "icon",
type: "image/png",
href: "/favicon.png",
},
],
script: [
{
src: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js",
integrity:
"sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz",
crossorigin: "anonymous",
},
],
},
},
experimental: {
payloadExtraction: false,
},
})

10647
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

21
package.json Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"axios": "^1.6.8",
"nuxt": "^3.11.2",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
},
"devDependencies": {
"sass": "^1.76.0"
}
}

53
pages/about.vue Normal file
View File

@@ -0,0 +1,53 @@
<template>
<div>
<div class="row mb-4">
<div class="d-flex align-items-center flex-column">
<div class="col-md-6">
<h1 class="display-4">About <TheTitle /> </h1>
<p class="lead">The <TheTitle /> project was started after experiencing stumbling blocks
with a shelved project.
</p>
<p>Our purpose is to provide League of Legends game data that is easy
to view for the common person. This game data is provided by CommunityDragon, another community resource.
</p>
<p>Therefore, no computer programming or scripting is needed.
Not everyone can browse a JSON or navigate game data folders. :3</p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 d-flex justify-content-center">
<div style="width: 250px !important;" class="d-flex flex-column justify-content-center align-items-center gap-1 border border-light border-opacity-25 bg-blur-3 rounded">
<img class="img-fluid rounded mb-2" src="/img/avatar.png"/>
<div class="px-3">
<div class="d-flex gap-2">
<NuxtLink class="text-decoration-none d-inline-block text-light m-0" to="https://blossomishymae.github.io"><h5 class="text-light mb-0">Blossomi Shymae</h5></NuxtLink>
<NuxtLink class="text-decoration-none d-inline-block text-light m-0" to="https://github.com/BlossomiShymae"><span><MaterialIcon name="github" :size="20" /> </span></NuxtLink>
</div>
<h5 class="fw-bold m-0 mb-2">Lonely elf girl</h5>
<p>She likes to chat with her friends and do random programming projects, such as this one.
</p>
</div>
</div>
</div>
<div class="col-md-6 d-flex flex-column justify-content-around">
<div>
<h3 class="fw-light">Technology stack</h3>
<p>This website uses Nuxt.js, the meta-framework of universal application.</p>
<p >Other libraries used include Bootstrap, the CSS framework, and Material Design for icons.</p>
</div>
<div>
<h3 class="fw-light">Contact</h3>
<p>Blossomi Shymae can be reached via her Discord server.</p>
</div>
</div>
</div>
</div>
</template>
<script>
import MaterialIcon from '~/components/MaterialIcon.vue';
import TheTitle from '~/components/TheTitle.vue';
</script>

57
pages/index.vue Normal file
View File

@@ -0,0 +1,57 @@
<template>
<div>
<div class="d-flex flex-column justify-content-center align-items-center gap-2" style="margin-top: 25%;">
<div class="d-flex flex-column justify-content-center align-items-center">
<h1 class="display-4"><TheTitle /></h1>
<p>Your local League of Legends companion index.</p>
</div>
</div>
<div class="row">
<div class="col-md-6 col-sm-12">
<h2>The <TheTitle /> Project</h2>
<p>Our primary purpose is to make game data viewable in a human-friendly
format without needing computer programming or scripting knowledge.
</p>
<p>CommunityDragon, the unofficial League of Legends data resource, is used for our project.</p>
</div>
<div class="col-md-6 col-sm-12">
<h2>Resources</h2>
<div class="row">
<div class="col-md-6 col-sm-12">
<div class="d-flex flex-column justify-content-center align-items-stretch gap-2">
<a class="btn btn-dark bg-transparent bg-blur-3 border-light border-opacity-25" asp-area="" asp-page="/Champion/Index">
<MaterialIcon name="account-group" :size="24"/> Champions
</a>
<a class="btn btn-dark bg-transparent bg-blur-3 border-light border-opacity-25">
<MaterialIcon name="magic-staff" :size="24"/> Items
</a>
<a class="btn btn-dark bg-transparent bg-blur-3 border-light border-opacity-25">
<MaterialIcon name="shield" :size="24"/> Runes
</a>
</div>
</div>
<div class="col-md-6 col-sm-12">
<div class="d-flex flex-column justify-content-center align-items-stretch gap-2">
<a class="btn btn-dark bg-transparent bg-blur-3 border-light border-opacity-25">
<MaterialIcon name="image" :size="24"/> Summoner Icons
</a>
<a class="btn btn-dark bg-transparent bg-blur-3 border-light border-opacity-25">
<MaterialIcon name="floor-lamp" :size="24"/> Ward Skins
</a>
<a class="btn btn-dark bg-transparent bg-blur-3 border-light border-opacity-25">
<MaterialIcon name="face-woman-shimmer" :size="24"/> Emotes
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import MaterialIcon from '~/components/MaterialIcon.vue';
import TheTitle from '~/components/TheTitle.vue';
</script>

49
pages/items/index.vue Normal file
View File

@@ -0,0 +1,49 @@
<template>
<div class="d-flex flex-column gap-2">
<h1>Items</h1>
<div class="overflow-hidden rounded border border-light border-opacity-25 p-4">
<table class="sortable table table-borderless">
<thead>
<tr>
<th scope="col">Id</th>
<th scope="col">Icon</th>
<th scope="col">Name</th>
<th scope="col">Price</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items" :key="item.id">
<th scope="row">
<NuxtLink class="text-decoration-none text-light" :to="`/items/overview/${item.id}`">
{{ item.id }}
</NuxtLink>
</th>
<td>
<NuxtLink class="text-decoration-none text-light" :to="`/items/overview/${item.id}`">
<img class="rounded" :src="item.getIcon('latest')" width="32" height="32" loading="lazy" onerror="this.onerror = null; this.src = '/img/error.png'"/>
</NuxtLink>
</td>
<td>
<NuxtLink class="text-decoration-none text-light" :to="`/items/overview/${item.id}`">
<span v-html="item.name"></span>
</NuxtLink>
</td>
<td>
<NuxtLink class="text-decoration-none text-light" :to="`/items/overview/${item.id}`">
{{ item.priceTotal }}
</NuxtLink>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script setup lang="ts">
import useClient from '../../composables/useClient';
const { client } = useClient();
const items = await client.items.listAsync({ locale: "default", version: "latest"});
</script>

View File

@@ -0,0 +1,68 @@
<template>
<div>
<div class="row">
<div class="col-md-6 col-sm-12">
<h1 class="display-4 mb-0"> {{ item.name }}</h1>
<p class="text-muted" v-html="item.description"></p>
</div>
<div class="col-md-6 col-sm-12 d-flex justify-content-center align-items-center">
<img class="border rounded border-dark" :src="item.getIcon('latest')"/>
</div>
</div>
<div class="row">
<div class="d-flex flex-wrap gap-3">
<Badge name="identifier">{{ item.id }}</Badge>
<Badge name="hand-coin">{{ item.priceTotal }}</Badge>
<Badge name="hand-coin-outline">{{ item.price }}</Badge>
<Badge name="keyboard-variant" v-if="item.active">Active</Badge>
</div>
</div>
<div class="row mt-4">
<div class="col-md-6 col-sm-12 border-start border-light border-opacity-25 border-4"
v-if="components.length > 0">
<h4 class="fw-light">Component</h4>
<div class="d-flex justify-content-around align-items-center gap-2 flex-wrap">
<NuxtLink v-for="component in components" :to="`/items/overview/${component.id}`" :key="component.id">
<img class="border rounded border-dark" :src="component.getIcon('latest')"/>
</NuxtLink>
</div>
</div>
<div class="col-md-6 col-sm-12 border-start border-light border-opacity-25 border-4"
v-if="composites.length > 0">
<h4 class="fw-light">Composite</h4>
<div class="d-flex justify-content-around align-items-center gap-2 flex-wrap">
<NuxtLink v-for="composite in composites" :to="`/items/overview/${composite.id}`" :key="composite.id">
<img class="border rounded border-dark" :src="composite.getIcon('latest')"/>
</NuxtLink>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
import useClient from '../../../composables/useClient';
import { Item } from '~/core/models';
import Badge from '~/components/Badge.vue';
const route = useRoute();
const id = route.params.id as unknown;
const { client } = useClient();
const items = await client.items.listAsync({locale: "default", version: "latest"});
const _default = new Item({});
const item = items.find((x) => x.id == id) || _default;
const components = item.from.map((id) => items.find((x) => x.id == id) || _default);
const composites = item.to.map((id) => items.find((x) => x.id == id) || _default);
</script>
<style lang="scss" scoped>
</style>

BIN
preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 KiB

0
public/.nojekyll Normal file
View File

133
public/css/app.css Normal file
View File

@@ -0,0 +1,133 @@
* {
color: white;
}
html {
font-size: 14px;
background-repeat: no-repeat;
background-size: cover;
background-attachment: fixed;
background-position: center;
min-height: 100%;
position: relative;
z-index: 0;
}
html::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: #0008;
z-index: 1;
backdrop-filter: blur(0.2rem);
}
body {
position: relative;
background-color: transparent;
z-index: 10;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.btn:focus,
.btn:active:focus,
.btn-link.nav-link:focus,
.form-control:focus,
.form-check-input:focus,
a:focus-visible {
box-shadow: 0.05rem 0.05rem 0.375rem 0.1rem rgba(255, 255, 255, 0.744) !important;
outline: none;
}
header {
top: 0;
position: sticky;
z-index: 1000;
background-color: #0004;
}
svg {
filter: invert(100%);
}
table {
background-color: #0004 !important;
}
thead, tbody, th, td {
background-color: transparent !important;
}
table > tbody > tr:nth-of-type(2n+1) > * {
background-color: #0004 !important;
}
table > tbody > tr:hover > * {
background-color: #0008 !important;
}
.background {
height: 100%;
width: 100%;
position: absolute;
}
.background-screen {
background-color: #0008;
z-index: -1;
}
.bg-screen {
background-color: #0004 !important;
}
.background-transparent {
background-color: transparent;
z-index: -1;
}
.background-blur {
backdrop-filter: blur(0.2rem);
}
.bg-blur {
backdrop-filter: blur(0.2rem);
}
.background-blur-2 {
backdrop-filter: blur(0.4rem);
}
.bg-blur-2 {
backdrop-filter: blur(0.4rem);
}
.bg-blur-3 {
backdrop-filter: blur(0.6rem);
}
.bg-blur-4 {
backdrop-filter: blur(0.8rem);
}
.z-index--10 {
z-index: -10;
}
.z-index-10 {
z-index: 10;
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
public/img/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

BIN
public/img/error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

3
server/tsconfig.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

4
tsconfig.json Normal file
View File

@@ -0,0 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

50
utils/paginated-array.ts Normal file
View File

@@ -0,0 +1,50 @@
export class PaginatedArray<T> extends Array<T> {
pageSize: number;
items: Array<T>;
pageIndex: number;
totalPages: number;
constructor(items: Array<T>, pageIndex: number, pageSize: number) {
super();
this.pageSize = pageSize;
this.items = items;
this.pageIndex = pageIndex;
this.totalPages = Math.ceil(items.length / pageSize);
this.push(...items.slice((pageIndex - 1) * pageSize).slice(0, pageSize));
}
public get hasFurtherPreviousPage() {
return this.pageIndex > 2;
}
public get hasPreviousPage() {
return this.pageIndex > 1;
}
public get hasNextPage() {
return this.pageIndex < this.totalPages;
}
public get hasFurtherNextPage() {
return this.pageIndex < this.totalPages - 1;
}
public get furtherPreviousPage() {
return this.pageIndex - 2;
}
public get previousPage() {
return this.pageIndex - 1;
}
public get nextPage() {
return this.pageIndex + 1;
}
public get furtherNextPage() {
return this.pageIndex + 2;
}
}