feat: component base

This commit is contained in:
Corentin Thomasset 2021-02-13 19:55:45 +01:00
parent 02dafd6a2f
commit 6e0c369398
No known key found for this signature in database
GPG key ID: DBD997E935996158
17 changed files with 1482 additions and 1006 deletions

6
assets/logo-outlined.svg Executable file
View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 82.09 82.06">
<g>
<path fill="#fff" d="M44,4a.14.14,0,0,1,.14.14V7.79a4,4,0,0,0,3.17,3.91,29.7,29.7,0,0,1,10,4.16,4,4,0,0,0,2.18.65,4,4,0,0,0,2.83-1.17l2.6-2.6a.12.12,0,0,1,.09,0,.13.13,0,0,1,.1,0l4.15,4.15a.14.14,0,0,1,0,.19l-2.6,2.6a4,4,0,0,0-.52,5,29.65,29.65,0,0,1,4.16,10.08,4,4,0,0,0,3.92,3.17H78a.13.13,0,0,1,.13.14V44a.13.13,0,0,1-.13.13H74.27a4,4,0,0,0-3.92,3.17,29.45,29.45,0,0,1-4.16,10,4,4,0,0,0,.52,5l2.62,2.61a.12.12,0,0,1,0,.09.13.13,0,0,1,0,.1l-4.15,4.15a.14.14,0,0,1-.2,0l-2.61-2.62a4,4,0,0,0-2.83-1.17,3.94,3.94,0,0,0-2.18.65,29.71,29.71,0,0,1-10,4.15,4,4,0,0,0-3.17,3.92v3.7A.14.14,0,0,1,44,78H38.15a.14.14,0,0,1-.14-.14v-3.7a4,4,0,0,0-2.7-3.78l2.94-3A26.71,26.71,0,0,0,49.4,66.28a27.1,27.1,0,0,0,3.66-1.51A26.72,26.72,0,0,0,64.84,53a26,26,0,0,0,1.52-3.67A26.68,26.68,0,0,0,53.08,17.21a27.4,27.4,0,0,0-3.68-1.52,26.78,26.78,0,0,0-16.64,0,27.89,27.89,0,0,0-3.71,1.54A26.72,26.72,0,0,0,17.29,29a27.17,27.17,0,0,0-1.53,3.7A26.8,26.8,0,0,0,14.6,43.81l-2.95,2.94a4,4,0,0,0-3.77-2.67H4.2A.13.13,0,0,1,4.07,44V38.08a.13.13,0,0,1,.13-.14H7.87a4,4,0,0,0,3.91-3.17A29.68,29.68,0,0,1,15.94,24.7a4,4,0,0,0-.52-5l-2.59-2.58a.14.14,0,0,1,0-.19L17,12.77a.14.14,0,0,1,.2,0l2.57,2.58a4,4,0,0,0,5,.52A29.54,29.54,0,0,1,34.84,11.7,4,4,0,0,0,38,7.79V4.14A.14.14,0,0,1,38.15,4H44m0-4H38.15A4.14,4.14,0,0,0,34,4.14V7.79a33.61,33.61,0,0,0-11.43,4.73L20,9.94a4.14,4.14,0,0,0-5.85,0L10,14.09a4.15,4.15,0,0,0,0,5.85l2.59,2.58A33.66,33.66,0,0,0,7.87,33.94H4.2A4.13,4.13,0,0,0,.07,38.08V44A4.13,4.13,0,0,0,4.2,48.08H7.88a33.09,33.09,0,0,0,1.94,6.16l9-9a22.74,22.74,0,0,1,.72-11.28,24.44,24.44,0,0,1,1.3-3.14,22.75,22.75,0,0,1,10-10A21.53,21.53,0,0,1,34,19.49a22.68,22.68,0,0,1,14.14,0A22.8,22.8,0,0,1,61.28,30.78a23.88,23.88,0,0,1,1.3,3.16,22.71,22.71,0,0,1,0,14.14A22.43,22.43,0,0,1,57.07,57a22.6,22.6,0,0,1-5.81,4.21,21.43,21.43,0,0,1-3.11,1.28,22.44,22.44,0,0,1-7.07,1.13,23.23,23.23,0,0,1-4.24-.39l-9,9a34.19,34.19,0,0,0,6.19,2v3.7A4.14,4.14,0,0,0,38.15,82H44a4.14,4.14,0,0,0,4.14-4.14v-3.7a33.71,33.71,0,0,0,11.39-4.72l2.62,2.62a4.14,4.14,0,0,0,5.85,0l4.15-4.15a4.15,4.15,0,0,0,0-5.85l-2.62-2.61a33.79,33.79,0,0,0,4.73-11.4H78A4.13,4.13,0,0,0,82.09,44V38.08A4.13,4.13,0,0,0,78,33.94H74.28a33.81,33.81,0,0,0-4.73-11.43l2.6-2.6a4.15,4.15,0,0,0,0-5.85L68,9.91a4.14,4.14,0,0,0-5.85,0l-2.6,2.6a33.85,33.85,0,0,0-11.4-4.72V4.14A4.14,4.14,0,0,0,44,0Z"/>
<path fill="#fff" d="M53.84,36.22,48.07,42a5.66,5.66,0,0,1-8,0h0a5.66,5.66,0,0,1,0-8l5.77-5.77a2.26,2.26,0,0,0-1.12-3.81A17,17,0,0,0,25.16,46.9L2.07,70a7.07,7.07,0,0,0,0,10h0a7.07,7.07,0,0,0,10,0L35.16,56.9A17,17,0,0,0,57.65,37.34,2.26,2.26,0,0,0,53.84,36.22Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

23
assets/small-hero-gradient.svg Executable file
View file

@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 275">
<defs>
<linearGradient id="small-hero-gradient-1" x1="13.74" y1="183.7" x2="303.96" y2="45.59" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#25636c"/>
<stop offset="0.6" stop-color="#3b956f"/>
<stop offset="1" stop-color="#47b171"/>
</linearGradient>
</defs>
<g>
<g>
<path fill="#00a19a" opacity="0.49" d="M0,187.5v25s0,37.5,50,50S300,225,300,225V187.5Z"/>
</g>
<g>
<path fill="#00a19a" opacity="0.49" d="M300,237.5S287.5,275,250,275,121.05,237.5,61.4,200s134.21,0,134.21,0Z"/>
</g>
<g>
<path fill="#00a19a" opacity="0.38" d="M0,200v12.5a241.47,241.47,0,0,0,112.5,50c73.6,11.69,130.61-14.86,150-25L300,200Z"/>
</g>
<g>
<path fill="url(#small-hero-gradient-1)" d="M0,0V212.5s62.5-12.5,150,25,150,0,150,0V0Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 895 B

46
components/SearchBar.vue Normal file
View file

@ -0,0 +1,46 @@
<template>
<v-autocomplete
label="Search..."
single-line
append-icon="fa-search"
color="white"
hide-details
:items="items"
item-text="text"
item-value="path"
solo-inverted
:filter="filter"
clearable
cache-items
@change="choose"
>
<template v-slot:no-data>
<v-list-item>
<v-list-item-title>
Search for the <strong>tool</strong> you need!
</v-list-item-title>
</v-list-item>
</template>
</v-autocomplete>
</template>
<script>
import {Component, Vue} from 'nuxt-property-decorator'
import {ToolRoutes} from '~/mixins/tool-routes'
@Component({
mixins: [ToolRoutes]
})
export default class SearchBar extends Vue {
title = 'IT - Tools'
drawer = false
items = []
}
</script>
<style scoped lang="less">
::v-deep .v-list-item__mask{
color: inherit !important;
background: inherit !important;
}
</style>

View file

@ -1,28 +1,29 @@
<script lang="ts">
import {Component, Vue} from 'nuxt-property-decorator'
import ToolWrapper from '~/components/ToolWrapper.vue';
interface ToolConfig {
title: string;
description: string;
}
export {ToolConfig}
import ToolWrapper from '~/components/ToolWrapper.vue'
import {ToolConfig} from '~/types/ToolConfig'
@Component({components: {ToolWrapper}})
export default class Tool extends Vue {
public isTool = true;
public config: ToolConfig = {
title: 'Tool',
description: 'Tool description'
}
config(): ToolConfig {
throw new Error('You need to specify a config() method your custom Tool.')
};
public head() {
const {title, description} = this.config
const {title, description, keywords} = this.config()
return {
title,
description
meta: [
{
name: 'description',
content: description
},
{
name: 'keywords',
content: keywords
}
]
}
}
}

View file

@ -4,12 +4,14 @@
<v-col cols="12" lg="6">
<div class="tool-wrapper-info">
<h1>{{ config.title }}</h1>
<div class="spacer"></div>
<div class="description">{{ config.description }}</div>
<div class="spacer" />
<div class="description">
{{ config.description }}
</div>
</div>
<v-card flat>
<v-card-text class="pa-10">
<slot></slot>
<slot />
</v-card-text>
</v-card>
</v-col>
@ -19,7 +21,7 @@
<script lang="ts">
import {Component, Prop, Vue} from 'nuxt-property-decorator'
import {ToolConfig} from '~/components/Tool.vue'
import {ToolConfig} from '~/types/ToolConfig'
@Component
export default class ToolWrapper extends Vue {

View file

@ -5,34 +5,40 @@
fixed
app
>
<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis corporis cumque dolore esse eveniet
exercitationem explicabo ipsa libero necessitatibus numquam optio pariatur, perferendis placeat porro ullam vel
velit voluptas voluptates?
<div class="small-hero">
<HeroGradient />
<div class="small-hero-content">
<div class="small-hero-content-logo">
<LogoOutlined />
</div>
<div class="small-hero-content-title">
{{ title }}
</div>
</div>
</div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
<path fill="#eee" fill-opacity="1"
d="M0,160L26.7,133.3C53.3,107,107,53,160,58.7C213.3,64,267,128,320,176C373.3,224,427,256,480,250.7C533.3,245,587,203,640,186.7C693.3,171,747,181,800,186.7C853.3,192,907,192,960,186.7C1013.3,181,1067,171,1120,176C1173.3,181,1227,203,1280,192C1333.3,181,1387,139,1413,117.3L1440,96L1440,0L1413.3,0C1386.7,0,1333,0,1280,0C1226.7,0,1173,0,1120,0C1066.7,0,1013,0,960,0C906.7,0,853,0,800,0C746.7,0,693,0,640,0C586.7,0,533,0,480,0C426.7,0,373,0,320,0C266.7,0,213,0,160,0C106.7,0,53,0,27,0L0,0Z"></path>
<path fill="#05e677" fill-opacity="1"
d="M0,224L26.7,218.7C53.3,213,107,203,160,213.3C213.3,224,267,256,320,266.7C373.3,277,427,267,480,256C533.3,245,587,235,640,208C693.3,181,747,139,800,106.7C853.3,75,907,53,960,69.3C1013.3,85,1067,139,1120,181.3C1173.3,224,1227,256,1280,266.7C1333.3,277,1387,267,1413,261.3L1440,256L1440,0L1413.3,0C1386.7,0,1333,0,1280,0C1226.7,0,1173,0,1120,0C1066.7,0,1013,0,960,0C906.7,0,853,0,800,0C746.7,0,693,0,640,0C586.7,0,533,0,480,0C426.7,0,373,0,320,0C266.7,0,213,0,160,0C106.7,0,53,0,27,0L0,0Z"></path>
</svg>
<v-list>
<v-list-item
v-for="(item, i) in items"
:key="i"
:to="item.to"
router
exact
dense
>
<v-list-item-action>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title v-text="item.title"/>
</v-list-item-content>
</v-list-item>
<div v-for="(items, section) in toolRoutesSections" :key="section">
<v-subheader class="mt-4 pl-4">
{{ section }}
</v-subheader>
<v-list-item
v-for="(item, i) in items"
:key="i"
:to="items.path"
router
exact
dense
>
<v-list-item-action>
<v-icon>{{ item.config.icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title v-text="item.config.title" />
</v-list-item-content>
</v-list-item>
</div>
</v-list>
</v-navigation-drawer>
@ -41,14 +47,14 @@
flat
height="60px"
>
<v-app-bar-nav-icon @click.stop="drawer = !drawer"/>
<v-toolbar-title v-text="title"/>
<v-spacer/>
<v-app-bar-nav-icon @click.stop="drawer = !drawer" />
<v-toolbar-title v-if="!drawer" v-text="title" />
<v-spacer />
</v-app-bar>
<v-main>
<v-container>
<nuxt/>
<nuxt />
</v-container>
</v-main>
<!-- <v-footer app>-->
@ -58,42 +64,63 @@
</template>
<script lang="ts">
import {Component, Vue} from 'nuxt-property-decorator';
import {Component, mixins} from 'nuxt-property-decorator'
import {ToolRoutes} from '~/mixins/tool-routes'
import LogoOutlined from '~/assets/logo-outlined.svg?inline'
import HeroGradient from '~/assets/small-hero-gradient.svg?inline'
@Component
export default class DefaultLayout extends Vue {
@Component({
components: {
LogoOutlined,
HeroGradient
}
})
export default class DefaultLayout extends mixins(ToolRoutes) {
title = 'IT - Tools'
drawer = false
items = [
{
icon: 'mdi-apps',
title: 'Welcome',
to: '/'
},
{
icon: 'mdi-apps',
title: 'Token generator',
to: '/crypto/TokenGenerator'
},
{
icon: 'mdi-chart-bubble',
title: 'Inspire',
to: '/inspire'
}
]
created() {
}
items = []
}
</script>
<style lang="less">
.small-hero {
position: relative;
.small-hero-content {
padding-top: 30px;
position: absolute;
top: 0;
left: 0;
text-align: center;
width: 100%;
.small-hero-content-logo {
width: 30%;
margin: 0 auto;
}
.small-hero-content-title {
font-size: 30px;
font-weight: 600;
font-family: Ubuntu, Roboto, sans-serif;
}
}
}
.v-application {
background-color: var(--v-background-base, #121212) !important;
}
.v-snack {
background: none !important;
}
.v-snack__content {
font-weight: bold !important;
color: #fff !important;
}
.theme--dark {
.v-card,
.v-footer,

View file

@ -1,3 +1,5 @@
import {Component, Vue} from 'nuxt-property-decorator'
const copyToClipboard = (text: string) => {
const input = document.createElement('textarea')
input.innerHTML = text
@ -8,11 +10,11 @@ const copyToClipboard = (text: string) => {
return result
}
export const copyable = {
methods: {
copy(text: string, toastText = 'Copied to clipboard !') {
copyToClipboard(text)
this.$toast.success(toastText)
}
@Component
export class Copyable extends Vue {
copy(text: string, toastText = 'Copied to clipboard !') {
copyToClipboard(text)
console.log(toastText)
this.$toast.success(toastText)
}
}

33
mixins/tool-routes.ts Normal file
View file

@ -0,0 +1,33 @@
import {Component, Vue} from 'nuxt-property-decorator'
import {RouteConfig} from '@nuxt/types/config/router'
import {ToolConfig} from '~/types/ToolConfig'
import {capitalise} from '~/utils/string'
export type ToolRouteConfig = RouteConfig & {config: ToolConfig}
@Component
export class ToolRoutes extends Vue {
toolRoutesFlat : ToolRouteConfig[] = []
toolRoutesSections : {[key: string]: ToolRouteConfig[]} = {}
async mounted() {
const routes = this.$router.options.routes?.filter(r => r.meta?.isTool) || []
for (const route of routes) {
if ('component' in route) {
// @ts-ignore
const component = await route.component()
const routeConfig = {...route, config: component.options.methods.config()} as ToolRouteConfig
this.toolRoutesFlat.push(routeConfig)
const sectionKey = capitalise(route.meta.section).replace(/_/g, ' ')
if (!(sectionKey in this.toolRoutesSections)) {
this.toolRoutesSections[sectionKey] = []
}
this.toolRoutesSections[sectionKey].push(routeConfig)
}
}
}
}

View file

@ -9,21 +9,23 @@ export default {
// Global page headers (https://go.nuxtjs.dev/config-head)
head: {
titleTemplate: '%s - it-tools',
title: 'it-tools',
titleTemplate: '%s - IT-Tools',
title: 'IT-Tools',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' }
{charset: 'utf-8'},
{name: 'viewport', content: 'width=device-width, initial-scale=1'},
{hid: 'description', name: 'description', content: ''}
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
link: [{rel: 'icon', type: 'image/x-icon', href: '/favicon.ico'}]
},
// Global CSS (https://go.nuxtjs.dev/config-css)
css: [],
// Plugins to run before rendering page (https://go.nuxtjs.dev/config-plugins)
plugins: [],
plugins: [
'~/plugins/vuetify-toast'
],
// Auto import components (https://go.nuxtjs.dev/config-components)
components: true,
@ -41,7 +43,8 @@ export default {
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
// https://go.nuxtjs.dev/pwa
'@nuxtjs/pwa'
'@nuxtjs/pwa',
'@nuxtjs/svg'
],
// Axios module configuration (https://go.nuxtjs.dev/config-axios)
@ -50,10 +53,16 @@ export default {
// Vuetify module configuration (https://go.nuxtjs.dev/config-vuetify)
vuetify: {
customVariables: ['~/assets/variables.scss'],
treeShake: true,
treeShake: {
components: [
'VSnackbar',
'VBtn',
'VIcon'
]
},
theme: {
dark: true,
options: { customProperties: true },
options: {customProperties: true},
themes: {
dark: {
primary: '#05e677',
@ -72,5 +81,20 @@ export default {
},
// Build Configuration (https://go.nuxtjs.dev/config-build)
build: {}
build: {},
router: {
extendRoutes(routes) {
routes.forEach((route) => {
if (route.path.match(/^\/tools\/.*/)) {
const sections = route.path.split('/')
route.path = `/${sections[sections.length - 1]}`
route.meta = {
isTool: true,
section: sections[sections.length - 2]
}
}
})
}
}
}

2076
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -12,11 +12,12 @@
"test": "jest"
},
"dependencies": {
"@nuxt/typescript-runtime": "^2.0.0",
"@nuxt/typescript-runtime": "^2.0.1",
"@nuxtjs/axios": "^5.12.2",
"@nuxtjs/pwa": "^3.0.2",
"@nuxtjs/toast": "^3.3.1",
"core-js": "^3.6.5",
"nuxt": "^2.14.6",
"nuxt": "^2.14.12",
"vuetify-toast-snackbar": "^0.6.1"
},
"devDependencies": {
@ -25,6 +26,7 @@
"@nuxtjs/eslint-config": "^3.1.0",
"@nuxtjs/eslint-config-typescript": "^3.0.0",
"@nuxtjs/eslint-module": "^2.0.0",
"@nuxtjs/svg": "^0.1.12",
"@nuxtjs/vuetify": "^1.11.2",
"@vue/test-utils": "^1.1.0",
"babel-core": "7.0.0-bridge.0",

View file

@ -1,5 +1,5 @@
<template>
<ToolWrapper :config="config">
<ToolWrapper :config="config()">
<v-row no-gutters>
<v-col lg="6" md="12">
<v-switch v-model="withLowercase" label="Lowercase (abc...)" />
@ -19,14 +19,18 @@
<v-btn depressed class="mr-4" @click="refreshToken()">
Refresh
</v-btn>
<!-- <v-btn @click="copyToken()" depressed>Copy token</v-btn>-->
<v-btn depressed @click="copy(token)">
Copy token
</v-btn>
</div>
</ToolWrapper>
</template>
<script lang="ts">
import {Component} from 'nuxt-property-decorator'
import Tool, {ToolConfig} from '~/components/Tool.vue'
import Tool from '~/components/Tool.vue'
import {ToolConfig} from '~/types/ToolConfig'
import {Copyable} from '~/mixins/copyable'
const shuffle = (s: string) => s.split('').sort(() => 0.5 - Math.random()).join('')
const lowercase = 'abcdefghijklmopqrstuvwxyz'
@ -34,11 +38,17 @@ const uppercase = 'ABCDEFGHIJKLMOPQRSTUVWXYZ'
const numbers = '0123456789'
const specials = '.,;:!?./-"\'#{([-|\\@)]=}*+'
@Component
@Component({
mixins: [Copyable]
})
export default class TokenGenerator extends Tool {
config: ToolConfig = {
title: 'Token generator',
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.'
config(): ToolConfig {
return {
title: 'Token generator',
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.',
icon: 'mdi-key-chain-variant',
keywords: ['token', 'random', 'string', 'alphanumeric', 'symbols']
}
}
withNumbers = true;
@ -53,7 +63,10 @@ export default class TokenGenerator extends Tool {
}
get token() {
if (this.refreshBool) { (() => {})() } // To force recomputation
if (this.refreshBool) {
(() => {
})()
} // To force recomputation
let result = ''
if (this.withLowercase) {

9
plugins/vuetify-toast.ts Normal file
View file

@ -0,0 +1,9 @@
import Vue from 'vue'
import VuetifyToast from 'vuetify-toast-snackbar'
// @ts-ignore
export default ({ $vuetify }) => {
Vue.use(VuetifyToast, {
$vuetify
})
}

View file

@ -14,6 +14,7 @@
"strict": true,
"noEmit": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"baseUrl": ".",
"paths": {
"~/*": [
@ -25,7 +26,9 @@
},
"types": [
"@types/node",
"@nuxt/types"
"@nuxtjs/toast",
"@nuxt/types",
"~/types/custom.d.ts"
]
},
"exclude": [

10
types/ToolConfig.ts Normal file
View file

@ -0,0 +1,10 @@
interface ToolConfig {
title: string;
description: string;
icon: string;
keywords: string[];
}
type ToolConfigMethod = () => ToolConfig;
export {ToolConfig, ToolConfigMethod}

6
types/custom.d.ts vendored Normal file
View file

@ -0,0 +1,6 @@
declare module '*.svg?inline' {
import { Component } from 'vue'
const content: Component
export default content
}

5
utils/string.ts Normal file
View file

@ -0,0 +1,5 @@
function capitalise(s: string) {
return s.charAt(0).toUpperCase() + s.slice(1)
}
export {capitalise}