feat: added memo base

This commit is contained in:
Corentin Thomasset 2021-03-14 23:37:57 +01:00
parent 1034296359
commit 0a4abde23d
No known key found for this signature in database
GPG key ID: DBD997E935996158
16 changed files with 537 additions and 151 deletions

View file

@ -13,132 +13,121 @@
<v-progress-circular
indeterminate
color="primary"
/>
</div>
<div v-else>
<p>Drag & drop a file here</p>
<p class="or">
or
</p>
<v-btn depressed @click="manualUploadClicked">
select a file
</v-btn>
<p class="or">or</p>
<v-btn depressed @click="manualUploadClicked">select a file</v-btn>
<input ref="uploadInput" type="file" hidden @change="(e) => handleFiles(e.target.files[0])">
<div v-if="allowUrl">
<p class="or">
or
</p>
<p class="or">or</p>
<v-text-field
ref="urlInput"
@click:append="urlFilled(url)"
@keypress.enter="urlFilled(url)"
v-model="url"
append-icon="fa-arrow-right"
dense
label="Paste the file url"
outlined
:error-messages="urlErrors"
@click:append="urlFilled(url)"
@keypress.enter="urlFilled(url)"
/>
</div>
</div>
</div>
</template>
<script lang="ts">
import {Component, Prop, Vue} from 'nuxt-property-decorator'
@Component
export default class FileUploader extends Vue {
@Prop({default: true}) readonly allowUrl!: boolean
dragging = false
urlErrors = null
dragEnterCounter = 0
url = ''
loading = false
imageDropped(e) {
this.dragging = false
if (e.dataTransfer.items.length > 0) {
const item = e.dataTransfer.items[0]
switch (item.kind) {
case 'string':
item.getAsString(url => this.urlFilled(url))
break
case 'file':
this.handleFiles(item.getAsFile())
break
<script>
import * as axios from "axios";
export default {
name: "FileUploader",
props: {
allowUrl: {
type: Boolean,
default: true
}
},
data() {
return {
dragging: false,
urlErrors: undefined,
dragEnterCounter: 0,
url: '',
loading: false
}
},
methods: {
imageDropped(e) {
this.dragging = false;
if (e.dataTransfer.items.length > 0) {
const item = e.dataTransfer.items[0];
switch (item.kind) {
case 'string':
item.getAsString(url => this.urlFilled(url));
break;
case 'file':
this.handleFiles(item.getAsFile());
break;
}
}
}
}
dragEnter() {
this.dragEnterCounter++
this.dragging = true
}
dragLeave() {
if (--this.dragEnterCounter <= 0) {
this.dragging = false
}
}
async urlFilled(url) {
if (url && url.length > 0) {
this.loading = true
try {
const {data, headers} = await axios.get(url)
const name = url.split('/').pop()
const file = new File([data], name, {type: headers['content-type']})
this.handleFiles(file)
} catch (ignored) {
this.urlErrors = 'Incorrect url'
},
dragEnter() {
this.dragEnterCounter++;
this.dragging = true;
},
dragLeave() {
if (--this.dragEnterCounter <= 0) {
this.dragging = false;
}
this.loading = false
},
async urlFilled(url) {
if (url && url.length > 0) {
this.loading = true;
try {
const {data, headers} = await axios.get(url);
const name = url.split('/').pop();
const file = new File([data], name, {type: headers['content-type']})
this.handleFiles(file);
} catch (ignored) {
this.urlErrors = 'Incorrect url'
}
this.loading = false;
}
},
dragEnd() {
this.dragging = false;
},
dragExit() {
this.dragging = false;
},
handleFiles(file) {
if (!file) return;
this.$emit('input', file)
},
manualUploadClicked() {
this.$refs.uploadInput.click()
}
}
dragEnd() {
this.dragging = false
}
dragExit() {
this.dragging = false
}
handleFiles(file) {
if (!file) {
return
}
this.$emit('input', file)
}
manualUploadClicked() {
this.$refs.uploadInput.click()
}
}
</script>
<style lang="less">
.drop-area {
border: 2px dashed rgba(255,255,255,0.3);
border: 2px dashed #363636;
border-radius: 10px;
background-color: rgba(255, 255, 255, 0.03);
& > *, .v-btn {
margin: 0 !important;
}
.or {
opacity: 0.7;
margin: 5px 0 !important;
}
&.drag-over {
border-color: var(--v-primary-base);
}
.v-input__icon {
button {
margin-top: 0 !important;
@ -146,3 +135,152 @@ export default class FileUploader extends Vue {
}
}
</style>
<!--<template>-->
<!-- <div-->
<!-- class="drop-area pa-4 text-center"-->
<!-- :class="{'drag-over':dragging, 'pb-0':!loading}"-->
<!-- @dragover.prevent-->
<!-- @drop.prevent="imageDropped"-->
<!-- @dragenter="dragEnter()"-->
<!-- @dragend="dragEnd()"-->
<!-- @dragleave="dragLeave()"-->
<!-- @dragexit="dragExit()"-->
<!-- >-->
<!-- <div v-if="loading">-->
<!-- <v-progress-circular-->
<!-- indeterminate-->
<!-- color="primary"-->
<!-- />-->
<!-- </div>-->
<!-- <div v-else>-->
<!-- <p>Drag & drop a file here</p>-->
<!-- <p class="or">-->
<!-- or-->
<!-- </p>-->
<!-- <v-btn depressed @click="manualUploadClicked">-->
<!-- select a file-->
<!-- </v-btn>-->
<!-- <input ref="uploadInput" type="file" hidden @change="(e) => handleFiles(e.target.files[0])">-->
<!-- <div v-if="allowUrl">-->
<!-- <p class="or">-->
<!-- or-->
<!-- </p>-->
<!-- <v-text-field-->
<!-- ref="urlInput"-->
<!-- v-model="url"-->
<!-- append-icon="fa-arrow-right"-->
<!-- dense-->
<!-- label="Paste the file url"-->
<!-- outlined-->
<!-- :error-messages="urlErrors"-->
<!-- @click:append="urlFilled(url)"-->
<!-- @keypress.enter="urlFilled(url)"-->
<!-- />-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!--</template>-->
<!--<script lang="ts">-->
<!--import {Component, Prop, Vue} from 'nuxt-property-decorator'-->
<!--@Component-->
<!--export default class FileUploader extends Vue {-->
<!-- @Prop({default: true}) readonly allowUrl!: boolean-->
<!-- dragging = false-->
<!-- urlErrors: string|null = null-->
<!-- dragEnterCounter = 0-->
<!-- url = ''-->
<!-- loading = false-->
<!-- imageDropped(e: DragEvent) {-->
<!-- this.dragging = false-->
<!-- if (e.dataTransfer.items.length > 0) {-->
<!-- const item = e.dataTransfer.items[0]-->
<!-- switch (item.kind) {-->
<!-- case 'string':-->
<!-- item.getAsString((url: string) => this.urlFilled(url))-->
<!-- break-->
<!-- case 'file':-->
<!-- this.handleFiles(item.getAsFile())-->
<!-- break-->
<!-- }-->
<!-- }-->
<!-- }-->
<!-- dragEnter() {-->
<!-- this.dragEnterCounter++-->
<!-- this.dragging = true-->
<!-- }-->
<!-- dragLeave() {-->
<!-- if (&#45;&#45;this.dragEnterCounter <= 0) {-->
<!-- this.dragging = false-->
<!-- }-->
<!-- }-->
<!-- async urlFilled(url: string) {-->
<!-- if (url && url.length > 0) {-->
<!-- this.loading = true-->
<!-- try {-->
<!-- const {data, headers} = await this.$axios.$get(url)-->
<!-- const name = url.split('/').pop()-->
<!-- const file = new File([data], name, {type: headers['content-type']})-->
<!-- this.handleFiles(file)-->
<!-- } catch (ignored) {-->
<!-- this.urlErrors = 'Incorrect url'-->
<!-- }-->
<!-- this.loading = false-->
<!-- }-->
<!-- }-->
<!-- dragEnd() {-->
<!-- this.dragging = false-->
<!-- }-->
<!-- dragExit() {-->
<!-- this.dragging = false-->
<!-- }-->
<!-- handleFiles(file: File) {-->
<!-- if (!file) {-->
<!-- return-->
<!-- }-->
<!-- this.$emit('input', file)-->
<!-- }-->
<!-- manualUploadClicked() {-->
<!-- this.$refs.uploadInput.click()-->
<!-- }-->
<!--}-->
<!--</script>-->
<!--<style lang="less">-->
<!--.drop-area {-->
<!-- border: 2px dashed rgba(255, 255, 255, 0.3);-->
<!-- border-radius: 10px;-->
<!-- background-color: rgba(255, 255, 255, 0.03);-->
<!-- & > *, .v-btn {-->
<!-- margin: 0 !important;-->
<!-- }-->
<!-- .or {-->
<!-- opacity: 0.7;-->
<!-- margin: 5px 0 !important;-->
<!-- }-->
<!-- &.drag-over {-->
<!-- border-color: var(&#45;&#45;v-primary-base);-->
<!-- }-->
<!-- .v-input__icon {-->
<!-- button {-->
<!-- margin-top: 0 !important;-->
<!-- }-->
<!-- }-->
<!--}-->
<!--</style>-->

103
components/Memo.vue Normal file
View file

@ -0,0 +1,103 @@
<template>
<div class="memo">
<ToolHeader :config="memoConfig" />
Warning: le style/aspect est toujours en wip, so focus on content <br><br>
<v-row>
<v-col cols="12" sm="12" md="6" lg="4" v-for="item in items" :key="item.section" class="memo-section">
<v-card class="mb-5">
<v-card-title>{{ item.section }}</v-card-title>
<v-card-text>
<div v-for="tip in item.items" :key="tip.text" class="memo-item">
<div class="memo-item-title">
{{ tip.text }}
</div>
<div v-if="tip.subtitle" class="memo-item-subtitle">
{{ tip.subtitle }}
</div>
<div v-if="tip.code" class="memo-item-code" @click="copy(tip.code)">
<pre>{{ tip.code }}</pre>
<v-icon>mdi-content-copy</v-icon>
</div>
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
</div>
</template>
<script lang="ts">
import {Component} from 'nuxt-property-decorator'
import Tool from './Tool.vue'
import type {ToolConfig} from '~/types/ToolConfig'
import {CopyableMixin} from '~/mixins/copyable.mixin'
import ToolHeader from '~/components/ToolHeader.vue'
type MemoItems = { section: string, items: { text: string, code?: string, subtitle?: string }[] }[];
@Component({
mixins: [CopyableMixin],
components: {ToolHeader}
})
export default class Memo extends Tool {
private memoConfig: ToolConfig = this.config();
private items: MemoItems = this.$t('memo') as unknown as MemoItems
}
</script>
<style lang="less">
.memo {
h1 {
font-weight: 300;
font-size: 50px;
margin: 0;
padding: 0;
}
h2 {
font-weight: 400;
}
.memo-item {
margin-bottom: 20px;
.memo-item-title {
font-weight: bold;
font-size: 16px;
color: var(--v-primary-base);
}
.memo-item-subtitle {
padding-bottom: 5px;
}
.memo-item-code {
cursor: pointer;
background-color: rgba(0, 0, 0, 0.1);
border-radius: 4px;
padding: 8px 15px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
pre {
flex: 1;
overflow-x: auto;
}
.v-icon {
opacity: 0;
}
&:hover {
.v-icon {
opacity: 1;
}
}
}
}
}
</style>

View file

@ -1,8 +1,7 @@
<script lang="ts">
import {Component, Vue} from 'nuxt-property-decorator'
import ToolWrapper from '~/components/ToolWrapper.vue'
import {ToolConfig} from '~/types/ToolConfig'
import type {ToolConfig} from '~/types/ToolConfig'
@Component({components: {ToolWrapper}})
export default class Tool extends Vue {
config(): ToolConfig {

44
components/ToolHeader.vue Normal file
View file

@ -0,0 +1,44 @@
<template>
<div class="tool-wrapper-info">
<h1>{{ config.title }}</h1>
<div class="spacer" />
<div class="description">
{{ config.description }}
</div>
</div>
</template>
<script lang="ts">
import {Component, Prop, Vue} from 'nuxt-property-decorator'
import type {ToolConfig} from '@/types/ToolConfig'
@Component
export default class ToolWrapper extends Vue {
@Prop() readonly config!: ToolConfig;
}
</script>
<style scoped lang="less">
.tool-wrapper-info {
padding: 50px 0 30px;
h1 {
font-weight: 300;
font-size: 50px;
margin: 0;
padding: 0;
}
.spacer {
width: 200px;
height: 2px;
background: var(--v-primary-base);
background: linear-gradient(90deg, rgba(71, 177, 113, 1) 0%, rgba(59, 149, 111, 1) 60%, rgba(37, 99, 108, 1) 200%);
margin-bottom: 10px;
}
.description {
color: #829097;
}
}
</style>

View file

@ -2,24 +2,18 @@
<div class="tool-wrapper">
<v-row no-gutters justify="center" align="center">
<v-col cols="12" xl="6" lg="8" md="10">
<div class="tool-wrapper-info">
<h1>{{ config.title }}</h1>
<div class="spacer"/>
<div class="description">
{{ config.description }}
</div>
</div>
<ToolHeader :config="config" />
<template v-if="!noCard">
<v-card flat>
<v-card-text class="pa-10">
<slot/>
<slot />
</v-card-text>
</v-card>
</template>
<template v-else>
<slot/>
<slot />
</template>
</v-col>
</v-row>
</div>
@ -27,9 +21,9 @@
<script lang="ts">
import {Component, Prop, Vue} from 'nuxt-property-decorator'
import {ToolConfig} from '~/types/ToolConfig'
@Component
import ToolHeader from './ToolHeader.vue'
import type {ToolConfig} from '~/types/ToolConfig'
@Component({components: {ToolHeader}})
export default class ToolWrapper extends Vue {
@Prop() readonly config!: ToolConfig;
@Prop({default: () => false}) readonly noCard!: boolean;
@ -41,31 +35,8 @@ export default class ToolWrapper extends Vue {
.tool-wrapper {
height: 100%;
.tool-wrapper-info {
padding: 50px 0 30px;
}
.category {
color: #546167;
}
h1 {
font-weight: 300;
font-size: 50px;
margin: 0;
padding: 0;
}
.spacer {
width: 200px;
height: 2px;
background: var(--v-primary-base);
background: linear-gradient(90deg, rgba(71, 177, 113, 1) 0%, rgba(59, 149, 111, 1) 60%, rgba(37, 99, 108, 1) 200%);
margin-bottom: 10px;
}
.description {
color: #829097;
}
}
</style>