refactor: ♻️ split up App.vue into separate components

This commit is contained in:
Marcus Mietz 2020-08-26 19:12:00 +02:00
parent 52edd0ef8a
commit ce817e59f1
8 changed files with 624 additions and 238 deletions

View file

@ -1,25 +1,31 @@
<template>
<table>
<thead>
<tr>
<template v-for="header in dataset.headers">
<th :key="header">{{ header }}</th>
</template>
<table class="datatable">
<thead class="datatable__head">
<tr class="datatable__row datatable__row--head">
<th
class="datatable__heading"
v-for="(header, idx) in dataset.headers"
:key="idx"
>
{{ header }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(tuple, row) in dataset.data" :key="row">
<template v-for="(item, col) in tuple">
<td :key="col" v-if="col === 1 && !isHexValue(item)" colspan="2">
{{ item }}
</td>
<td :key="item[0]" v-else>{{ item }}</td>
<td
v-if="isHexValue(item)"
:key="item[1]"
:style="'background-color:' + item"
></td>
</template>
<tbody class="datatable__body">
<tr class="datatable__row" v-for="(row, idx) in dataset.data" :key="idx">
<td
class="datatable__cell"
v-for="(cell, idx) in row"
:key="idx"
:colspan="idx === 1 && !isHexValue(row[1]) ? 2 : 1"
>
{{ cell }}
</td>
<td
v-if="isHexValue(row[1])"
class="datatable__cell--colorpick"
:style="`background-color: ${row[1]}`"
></td>
</tr>
</tbody>
</table>
@ -42,39 +48,48 @@ export default {
};
</script>
<style lang="scss" scoped>
table {
<style lang="scss">
@import "@scss/_variables.scss";
.datatable {
border-collapse: collapse;
width: 100%;
th,
td {
&__heading,
&__cell {
padding: 0.25em 0.5em;
@media (min-width: 35em) {
padding: 0.5em 1em;
}
}
}
thead {
tr {
background-color: #3f4c6b;
color: #fff;
}
}
&__body {
.datatable__row {
background-color: rgba($bg-color-dark, 0.7);
tbody {
tr {
background-color: #fff;
&:nth-child(odd) {
background-color: rgba(lighten($bg-color-dark, 20%), 0.7);
}
}
}
tr:nth-child(odd) {
background-color: #e1e1e1;
&__row {
&--head {
background-color: $bg-color-light;
color: $copy-color;
}
}
td:first-child {
text-align: right;
&__cell {
&:first-child {
text-align: right;
}
&--colorpick {
margin-top: 0.1em;
border: 0.1em solid #fff;
}
}
}
</style>

47
src/components/Figure.vue Normal file
View file

@ -0,0 +1,47 @@
<template>
<figure class="figure">
<slot name="img"></slot>
<figcaption class="figure__meta">
<slot name="caption"></slot>
<slot name="copyright"></slot>
</figcaption>
</figure>
</template>
<script>
export default {};
</script>
<style lang="scss">
@import "@scss/_variables.scss";
.figure {
margin: 1em 0;
&__meta {
text-align: center;
}
img {
display: block;
width: 100%;
}
label,
a {
font-size: 0.8em;
font-style: italic;
color: $copy-color;
}
label:after {
display: inline;
content: " ";
}
a:before {
display: inline;
content: "©";
}
}
</style>

63
src/components/Footer.vue Normal file
View file

@ -0,0 +1,63 @@
<template>
<footer class="footer">
<div class="footer__copyright">
© {{ new Date().getFullYear() }} Sebin Nyshkim
</div>
<ul class="footer__linklist">
<li class="footer__linkitem" v-for="(link, idx) in links" :key="idx">
<a :href="link.href" class="footer__link">
{{ link.text }}
</a>
</li>
</ul>
</footer>
</template>
<script>
export default {
data() {
return {
links: [
{ href: "https://twitter.com/SebinNyshkim", text: "Twitter" },
{ href: "https://t.me/SebinNyshkim", text: "Telegram" },
{
href: "https://www.furaffinity.net/user/sonofdragons",
text: "Fur Affinity"
}
]
};
}
};
</script>
<style lang="scss">
@import "@scss/_variables.scss";
.footer {
background-color: $bg-color-light;
padding: 0.5em 0;
text-align: center;
&__copyright {
margin: 0;
}
&__linklist {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-flow: row wrap;
justify-content: center;
}
&__linkitem {
}
&__link {
color: $copy-color;
padding: 0.5em 0.75em;
}
}
</style>

View file

@ -1,37 +1,24 @@
<template>
<div class="gallery">
<div class="big-image">
<figure class="image-container">
<div class="gallery__bigimage">
<figure class="gallery__imagecontainer">
<img :src="bigImage" alt="" />
<figcaption>
{{ images.indexOf(bigImage) + 1 }} / {{ images.length }}
</figcaption>
</figure>
</div>
<ul class="thumbnails">
<li class="thumbnail" v-for="(img, index) in images" :key="index">
<img
:src="img"
:alt="'Image' + index"
@click.prevent="setBigImage(index)"
/>
</li>
</ul>
<slot></slot>
</div>
</template>
<script>
export default {
name: "gallery",
props: {
images: {
type: Array,
required: true
}
},
data() {
return {
bigImage: ""
bigImage: "",
images: []
};
},
methods: {
@ -40,108 +27,100 @@ export default {
}
},
mounted() {
this.bigImage = this.images[0];
const images = Array.from(this.$el.querySelectorAll("li img"));
images.forEach((img, idx) => {
img.addEventListener("click", e => {
e.preventDefault();
this.setBigImage(idx);
});
});
this.images = images.map(img => img.src);
this.bigImage = images[0].src;
}
};
</script>
<style lang="scss" scoped>
.big-image {
width: 100%;
height: 40vh;
<style lang="scss">
@import "@scss/_variables.scss";
@media (min-width: 35em) {
height: 30em;
.gallery {
&__bigimage {
width: 100%;
height: 40vh;
@media (min-width: 35em) {
height: 30em;
}
display: flex;
justify-content: center;
align-content: center;
align-items: center;
background-color: $copy-color-darkgrey;
overflow: hidden;
}
display: flex;
justify-content: center;
align-content: center;
align-items: center;
&__imagecontainer {
position: relative;
margin: 0;
width: 100%;
height: 100%;
flex: 1;
display: flex;
justify-content: center;
align-content: center;
align-items: center;
box-shadow: inset 0 0 15em #000;
background-color: #444;
img {
align-self: center;
max-width: 100%;
max-height: 100%;
}
overflow: hidden;
}
.image-container {
position: relative;
margin: 0;
width: 100%;
height: 100%;
flex: 1;
display: flex;
justify-content: center;
align-content: center;
align-items: center;
box-shadow: inset 0 0 15em #000;
img {
align-self: center;
max-width: 100%;
max-height: 100%;
figcaption {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(#000, 0.5);
color: $copy-color;
text-align: center;
}
}
figcaption {
position: absolute;
bottom: 0;
left: 0;
right: 0;
ul {
display: flex;
list-style: none;
height: 6.5em;
margin: 0;
padding: 0.5em;
overflow-x: scroll;
background-color: $copy-color-darkgrey;
}
background-color: rgba(#000, 0.5);
color: #fff;
li {
flex: 1 0 auto;
text-align: center;
}
}
.thumbnails {
display: flex;
align-content: center;
align-items: center;
height: 6.5em;
padding: 0.5em;
overflow-x: scroll;
background-color: #888;
}
.thumbnail {
flex: 1 0 auto;
height: 100%;
max-width: 100%;
max-height: 100%;
display: flex;
justify-content: center;
align-content: center;
align-items: center;
& + .thumbnail {
margin-left: 0.5em;
}
img {
cursor: pointer;
box-sizing: border-box;
border: 0.25em solid #fff;
align-self: center;
height: 100%;
max-width: 100%;
max-height: 100%;
& + li {
margin-left: 0.5em;
}
img {
cursor: pointer;
border: 0.25em solid #fff;
align-self: center;
max-width: 100%;
max-height: 100%;
}
}
}
</style>

125
src/components/Header.vue Normal file
View file

@ -0,0 +1,125 @@
<template>
<header>
<div class="nsfw-bar">
<div class="nsfw-bar__content">
<div>
This Page contains materials considered NSFW. Do you wish to see such
content?
</div>
<nsfw-switch />
</div>
</div>
<div class="header flex flex--row flex--wrap flex--center flex--center-v">
<div class="header__image-container col col-m-6 col-3">
<img
class="header__image"
src="@img/sebin-smug-icon.png"
alt="Sebin Avatar"
/>
</div>
<div class="header__headings col col-m-12 col-7">
<h1 class="header__main-heading">{{ mainHeading }}</h1>
<h2 class="header__sub-heading">{{ subHeading }}</h2>
</div>
</div>
<ref-navbar :navlinks="navlinks" />
</header>
</template>
<script>
import NsfwSwitch from "@components/NsfwSwitch.vue";
import RefNavbar from "@components/Navbar.vue";
export default {
props: {
mainHeading: String,
subHeading: String
},
data() {
return {
navlinks: [
{ href: "#general", label: "General", nsfw: false },
{ href: "#anatomy", label: "Anatomy", nsfw: false },
{ href: "#wings", label: "Wings", nsfw: false },
{ href: "#head", label: "Head", nsfw: false },
{ href: "#upperbody", label: "Upper Body", nsfw: false },
{ href: "#penis", label: "Penis", nsfw: true },
{ href: "#clothes", label: "Clothing Styles", nsfw: false },
{ href: "#abilities", label: "Abilities", nsfw: false }
]
};
},
components: {
NsfwSwitch,
RefNavbar
}
};
</script>
<style lang="scss">
@import "@scss/_variables.scss";
header {
background-color: $bg-color-dark;
}
.header {
max-width: 40em;
margin: 1em auto;
&__image {
width: 100%;
border-radius: 100%;
border: 0.375em solid #fff;
box-shadow: 0.125em 0.125em 0.5em rgba(#000, 0.7);
}
&__main-heading,
&__sub-heading {
font-family: "Exo", sans-serif;
margin: 1rem 0;
text-align: center;
}
&__main-heading {
font-size: 2.125em;
font-weight: 900;
font-style: italic;
@media (min-width: 35em) {
font-size: 2.75em;
}
}
&__sub-heading {
font-size: 1em;
font-weight: 300;
font-style: italic;
}
}
.nsfw-bar {
background-color: $bg-color-light;
padding: 0.5em;
&__content {
display: flex;
flex-flow: row wrap;
justify-content: center;
align-items: center;
max-width: 40rem;
margin: auto;
div {
flex: 1 1 auto;
text-align: center;
}
label {
flex: 0 0 5em;
}
}
}
</style>

141
src/components/Navbar.vue Normal file
View file

@ -0,0 +1,141 @@
<template>
<nav class="nav" :class="{ 'nav--fixed': isFixed }" ref="nav">
<ul class="nav__list flex flex--row flex--nowrap flex--center-v">
<template v-for="(item, id) in navlinks">
<li class="nav__item" :key="id" v-show="isNsfw(item.nsfw)">
<a class="nav__link" href="#" v-scroll-to="item.href">
{{ item.label }}
</a>
<div class="nav__underline"></div>
</li>
</template>
</ul>
</nav>
</template>
<script>
import throttle from "lodash/throttle";
import debounce from "lodash/debounce";
export default {
props: {
navlinks: Array
},
data() {
return {
isFixed: false,
offsetTop: null
};
},
methods: {
setOffsetTop() {
this.offsetTop = this.$el.offsetTop;
this.$el.style.height = `${this.$refs.nav.scrollHeight}px`;
},
checkIsFixed() {
this.isFixed = this.offsetTop < window.scrollY;
},
isNsfw(item) {
if (this.$parent.$parent.nsfw) {
return true;
} else {
return this.$parent.$parent.nsfw === item;
}
}
},
mounted() {
window.addEventListener(
"scroll",
throttle(() => {
if (!this.offsetTop) {
this.setOffsetTop();
}
this.checkIsFixed();
}, 50)
);
window.addEventListener(
"resize",
debounce(() => {
this.setOffsetTop();
this.checkIsFixed();
}, 100)
);
}
};
</script>
<style lang="scss">
@import "@scss/base.scss";
.nav {
&--fixed .nav__list {
position: fixed;
top: 0;
left: 0;
right: 0;
}
}
.nav__list {
background-color: $bg-color-light;
margin: 0;
padding: 0;
list-style-type: none;
// box-shadow: 0em 0.75em 1.25em -1em #444;
z-index: 1;
overflow: auto;
width: 100%;
@include mq-desktop {
justify-content: center;
}
}
.nav__item {
padding: 0.5em 0.5em;
white-space: nowrap;
}
.nav__link {
color: $copy-color;
display: block;
margin-bottom: 0.25em;
text-decoration: none;
&:hover ~ .nav__underline {
&::before,
&::after {
width: 50%;
}
}
}
.nav__underline {
position: relative;
display: block;
width: 100%;
&::before,
&::after {
content: "";
position: absolute;
width: 0;
height: 0.125em;
background-color: $copy-color;
bottom: 0;
transition: all 0.2s ease-in-out;
}
&::before {
left: 50%;
}
&::after {
right: 50%;
}
}
</style>

View file

@ -0,0 +1,112 @@
<template>
<label class="nsfw-switch" for="nsfw-switch">
<div>😇</div>
<div class="toggle-wrap">
<input
v-model="$parent.$parent.nsfw"
type="checkbox"
name="nsfw"
id="nsfw-switch"
/>
<div class="toggle"></div>
</div>
<div>😈</div>
</label>
</template>
<script>
export default {};
</script>
<style lang="scss">
.nsfw-switch {
font-family: "apple color emoji", "segoe ui emoji", "noto color emoji",
"android emoji", "emojisymbols", "emojione mozilla", "twemoji mozilla",
"segoe ui symbol";
padding: 0.5em;
display: flex;
justify-content: center;
align-items: center;
align-content: center;
> div {
flex: 0 1 33%;
align-self: center;
line-height: 1;
text-align: center;
}
.toggle-wrap {
margin: 0 0.5em;
}
.toggle {
position: relative;
display: inline-block;
width: 46px;
height: 26px;
background-color: #e6e6e6;
border-radius: 23px;
vertical-align: text-bottom;
transition: all 0.3s linear;
&::before {
content: "";
position: absolute;
left: 0;
width: 42px;
height: 22px;
background-color: #fff;
border-radius: 11px;
transform: translate3d(2px, 2px, 0) scale3d(1, 1, 1);
transition: all 0.25s linear;
}
&::after {
content: "";
position: absolute;
left: 0;
width: 22px;
height: 22px;
background-color: #fff;
border-radius: 11px;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.24);
transform: translate3d(2px, 2px, 0);
transition: all 0.2s ease-in-out;
}
}
&:active {
.toggle::after {
width: 28px;
transform: translate3d(2px, 2px, 0);
}
input {
&:checked + .toggle::after {
transform: translate3d(16px, 2px, 0);
}
}
}
input {
position: absolute;
opacity: 0;
pointer-events: none;
&:checked + .toggle {
background-color: #4bd763;
&::before {
transform: translate3d(18px, 2px, 0) scale3d(0, 0, 0);
}
&::after {
transform: translate3d(22px, 2px, 0);
}
}
}
}
</style>

View file

@ -1,96 +0,0 @@
<template>
<figure :class="[{ float: imgPos }, imgPos]">
<img :src="imgSrc" :alt="caption" />
<figcaption>
<div class="caption">{{ caption }}</div>
<div class="copyright">
By <a :href="getLink(copyright)[0]">{{ getLink(copyright)[1] }}</a>
</div>
</figcaption>
</figure>
</template>
<script>
export default {
name: "rich-figure",
props: {
imgSrc: {
type: String,
required: true
},
caption: {
type: String,
required: true
},
copyright: {
type: String,
required: true
},
imgPos: String,
imgW: String
},
methods: {
getLink(username) {
if (username.match(/^(@)?([a-z0-9_]{1,30})$/i))
return this.formatTwitter(username);
if (username.match(/^(FA:)/)) return this.formatFuraffinity(username);
},
formatFuraffinity: username => {
let user = username.slice(3);
return [`https://www.furaffnity.net/user/${user}`, user];
},
formatTwitter: username => {
let user = username.slice(1);
return [`https://twitter.com/${user}`, username];
}
}
};
</script>
<style lang="scss" scoped>
figure {
position: relative;
overflow: hidden;
margin: 0;
line-height: 1;
figcaption {
position: absolute;
bottom: 0;
left: 0;
right: 0;
line-height: 1.5;
background: rgba(#000, 0.5);
color: #fff;
text-align: center;
}
a {
color: #fff;
}
img {
display: block;
width: 100%;
}
}
.float {
width: 100%;
@media (min-width: 35em) {
width: 20em;
&.left {
float: left;
margin: 0 0.75em 0.375em 0;
}
&.right {
float: right;
margin: 0 0 0.375em 0.75em;
}
}
}
</style>