diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/Footer.vue | 23 | ||||
-rw-r--r-- | src/components/Logo.vue | 64 | ||||
-rw-r--r-- | src/components/Navigation.vue | 121 | ||||
-rw-r--r-- | src/components/News.vue | 54 | ||||
-rw-r--r-- | src/components/ServerStatus.vue | 73 |
5 files changed, 335 insertions, 0 deletions
diff --git a/src/components/Footer.vue b/src/components/Footer.vue new file mode 100644 index 0000000..c454e3a --- /dev/null +++ b/src/components/Footer.vue @@ -0,0 +1,23 @@ +<template> + <footer class="footer"> + © 2004—{{ year }} The Mana World + </footer> +</template> + +<style scoped> +.footer { + text-align: right; + font-size: 8pt; + padding: 5px; +} +</style> + +<script lang="ts"> +import Vue from "vue" +import Component from "vue-class-component" + +@Component +export default class Copyright extends Vue { + year = Reflect.construct(Date, []).getFullYear(); +} +</script> diff --git a/src/components/Logo.vue b/src/components/Logo.vue new file mode 100644 index 0000000..ff81161 --- /dev/null +++ b/src/components/Logo.vue @@ -0,0 +1,64 @@ +<template> + <router-link tag="div" :to="{ name: 'home' }" class="logo"> + The Mana World + <!--<span>Feel the mana power growing inside you</span>--> + <span>A free open source 2D MMORPG in development</span> + </router-link> +</template> + +<style scoped> +/* + XXX: I couldn't find the font usued in the original PNG logo so I used something + similar-ish +*/ +@import url('https://fonts.googleapis.com/css?family=Carter+One&display=swap'); + +.logo { + /* this is all relative because our mobile site has to be responsive */ + background: url(../assets/logo.svg) no-repeat left top; /* FIXME: the -small logo is fugly */ + background-size: 12vw 12vw; + padding: 2vw 0 0 12vw; + font-family: 'Carter One', cursive; + font-size: 7vw; + text-shadow: 0.03ch 0.06ch #070905; + color: #34B039; + height: 11vw; + cursor: pointer; + + & span { + display: none; + } +} + +@media (min-width: 800px) { + .logo { + background-image: url(../assets/logo.svg); + background-size: 100px 100px; + padding: 10px 0 0 100px; + font-size: 3em; + height: 100px; + position: relative; + + & span { + display: inline; + position: absolute; + font-family: Helvetica; + font-size: 0.3em; + top: 72px; + left: 110px; + text-shadow: none; + color: #616260; + font-style: oblique; + } + } +} + +@media (max-width: 300px) { + .logo { + background: url(../assets/logo-extrasmall.svg) no-repeat left top; + background-size: 12vw 12vw; + font-weight: bold; + text-shadow: none; + } +} +</style> diff --git a/src/components/Navigation.vue b/src/components/Navigation.vue new file mode 100644 index 0000000..199290b --- /dev/null +++ b/src/components/Navigation.vue @@ -0,0 +1,121 @@ +<template> + <nav class="nav"> + <ul> + <li><router-link :class="{ 'custom-active': isHome }" :to="{ name: 'home' }">Home</router-link></li> + <li><router-link :to="{ name: 'registration' }">Create Account</router-link></li> + <li><a href="https://wiki.themanaworld.org/index.php/Downloads">Download</a></li> + <li><router-link :to="{ name: 'about' }">About</router-link></li> + <li><a href="https://wiki.themanaworld.org/index.php/FAQ">FAQ</a></li> <!-- we might want to put FAQ under About, or put About on the wiki --> + <li><router-link :class="{ 'custom-active': isSupport }" :to="{ name: 'support' }">Support</router-link></li> + <li><a href="https://wiki.themanaworld.org/">Wiki</a></li> + <li><a href="https://forums.themanaworld.org/">Forums</a></li> + </ul> + <!-- TODO: we want a server status component: https://api.themanaworld.org/api/tmwa/server --> + <div class="server"> + <span>Server Status</span> + <ServerStatus class="status"/> + </div> + <ul> + <span>Source Code</span> + <li><a href="https://github.com/themanaworld">The Mana World</a></li> + <li><a href="https://gitlab.com/evol">Evol Online</a></li> + <li><a href="https://gitlab.com/manaplus">ManaPlus</a></li> + <li><a href="https://github.com/bjorn/tiled">Tiled</a></li> + </ul> + </nav> +</template> + +<style scoped> +.nav { + background: #BA7A58; + color: #2f2e32; + border-radius: 0 0 15px 15px; + padding: 15px; + font-size: 14px; + + & span { + text-align: center; + display: block; + padding: 5px; + border-bottom: solid 1px #2f2e32; + } + + & a, & a:visited { + color: #2f2e32; + text-decoration: none; + display: block; + border: solid 1px #CBA083; + padding: 1ch; + + &:hover, &.router-link-exact-active, &.custom-active { + background: rgba(255,255,255,0.4); + border: solid 1px #2f2e32; + font-weight: bold; + } + } + + & ul, & div { + background: #CBA083; + margin: 0px; + padding: 0px; + border-radius: 5px; + border: solid 1px #2f2e32; + list-style: none; + margin-bottom: 13px; + } + + & ul li { + margin-left: 0.8ch; + margin-right: 0.8ch; + + &:first-of-type { + margin-top: 0.8ch; + } + + &:last-of-type { + margin-bottom: 0.8ch; + } + } + + + & .server > .status { + text-align: center; + font-weight: bolder; + border: 0; + border-radius: 0 0 5px 5px; + + &:hover { + background: rgba(255,255,255,0.4); + } + } +} + +@media (min-width: 1100px) { + .nav { + border-radius: 0 15px 15px 0; + } +} +</style> + +<script lang="ts"> +import { Component, Vue } from "vue-property-decorator"; +import RouteRecord from "vue-router"; +import ServerStatus from "@/components/ServerStatus.vue"; + +@Component({ + components: { + ServerStatus, + }, + computed: { + // XXX: find a better way to do this + + isSupport() { + return this.$route.path.startsWith("/recover"); + }, + isHome() { + return this.$route.path.startsWith("/news"); + } + } +}) +export default class NavigationV extends Vue {} +</script> diff --git a/src/components/News.vue b/src/components/News.vue new file mode 100644 index 0000000..8a58514 --- /dev/null +++ b/src/components/News.vue @@ -0,0 +1,54 @@ +<template> + <div class="news" v-if="count"> + <span v-if="!entries.length">(no news entries)</span> + + <article class="entry" v-for="entry in entries" :id="entry.date"> + <a :href="'#' + entry.date">{{ entry.title }}</a> + <time :datetime="entry.date" class="date">{{ entry.date }}</time> + <section class="body" v-html="entry.html"></section> + </article> + </div> +</template> + +<style scoped> +.news .entry { + margin-bottom: 1em; + + &:nth-of-type(1n + 2) { + margin-top: 2em; + } + + & > a { + text-decoration: none; + color: inherit; + font-weight: bold; + } + + & > .date { + float: right; + } + + & .body { + margin-top: 5px; + } +} +</style> + +<script lang="ts"> +import { Component, Prop, Vue } from "vue-property-decorator"; +import newsEntries from "@/assets/news.json"; + +interface NewsEntry { + title: string; + date: string; + author: string; + html: string; +} + +@Component +export default class News extends Vue { + @Prop({ default: Infinity }) private count!: number; + @Prop({ default: 0 }) private from!: number; + private entries: NewsEntry[] = (newsEntries as NewsEntry[]).slice(this.from, this.count); +} +</script> diff --git a/src/components/ServerStatus.vue b/src/components/ServerStatus.vue new file mode 100644 index 0000000..c35e916 --- /dev/null +++ b/src/components/ServerStatus.vue @@ -0,0 +1,73 @@ +<template> + <aside> + <a v-if="Online && Players" target="_blank" href="https://server.themanaworld.org">Online: {{Players}} players</a> + <a v-if="Online && !Players" target="_blank" href="https://server.themanaworld.org">Online</a> + <a v-if="!Online" class="offline" target="_blank" rel="noopener" href="https://www.youtube.com/watch?v=ILVfzx5Pe-A">Offline</a> + </aside> +</template> + +<style scoped> +aside :any-link { + text-decoration: none; + color: green; + display: block; + padding: 8px; + + &.offline { + color: #d42424; + } +} +</style> + +<script lang="ts"> +import Vue from "vue" +import Component from "vue-class-component" + +interface StatusResponse { + serverStatus: string; + playersOnline?: number; +} + +@Component +export default class ServerStatus extends Vue { + Players = 0; + Online = true; + + private async getStatus () { + const req = new Request(`${process.env.VUE_APP_API}/tmwa/server`, { + mode: "cors", + referrer: "no-referrer", + }); + + try { + const raw_response = await fetch(req); + const data: StatusResponse = await raw_response.json(); + + this.Online = data.serverStatus === "Online"; + this.Players = data.playersOnline || 0; + + if (Reflect.has(self, "localStorage")) { + localStorage.setItem("onlinePlayers", `${this.Players}`); + localStorage.setItem("serverOnline", this.Online ? "true": "false"); + } + } catch (err) { + // API unreachable (assume it's online anyway) + this.Online = true; + } + + setTimeout(this.getStatus, 8000); + } + + mounted () { + // use the last cached value to populate prior to first fetch: + if (Reflect.has(self, "localStorage")) { + this.Players = +(localStorage.getItem("onlinePlayers") || 99); + this.Online = !!(localStorage.getItem("serverOnline") || true); + } else { + this.Players = 99; + } + + this.getStatus(); + } +} +</script> |