Add frontend 2.0

This commit is contained in:
2022-01-22 19:28:06 +01:00
parent ff22127baa
commit e0efd9dc41
28 changed files with 1954 additions and 140 deletions

View File

@@ -1,14 +1,17 @@
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
import HelloWorld from './components/HelloWorld.vue';
import Input from './components/Input.vue';
import Nav from './components/Nav.vue';
import Settings from './components/Settings.vue';
import Renderer from './components/Renderer.vue';
</script>
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Hello Vue 3 + TypeScript + Vite" />
<Input />
<Nav />
<div class="flex grow space-x-8">
<div class="w-80">
<Settings />
</div>
<Renderer />
</div>
</template>
<style scoped>

View File

@@ -1,10 +0,0 @@
<script setup lang="ts">
defineProps<{ msg: string }>()
</script>
<template>
<h1>{{ msg }}</h1>
</template>
<style scoped>
</style>

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
</script>
<template>
<div>
<label class="block text-sm font-medium text-gray-700">Label</label>
<div class="mt-1 relative rounded-md shadow-sm">
<input type="text" class="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-12 sm:text-sm border-gray-300 rounded-md" />
</div>
</div>
</template>

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
const downloads = [
{ name: 'Linux', link: './assets/covergen.linux-amd64' },
{ name: 'Darwin', link: './assets/covergen.darwin-amd64' },
];
</script>
<template>
<div class="w-full mx-auto px-8">
<div class="relative flex items-center justify-between h-16">
<div class="flex-1 flex items-stretch justify-between">
<div class="flex-shrink-0 flex items-center">
<img
src="../assets/logo.png"
alt="covergen logo"
class="h-8 w-auto mr-2"
/>
<h1>CoverGen</h1>
</div>
<div class="ml-6">
<div class="flex space-x-4 items-center text-sm font-medium">
<span class="text-base"> Download me: </span>
<a
v-for="dl in downloads"
:key="dl.link"
:href="dl.link"
class="text-gray-800 hover:bg-gray-300 px-3 py-2 rounded-md"
>{{ dl.name }}</a
>
</div>
</div>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
import { useCover } from '@/stores/cover';
import { storeToRefs } from 'pinia';
const store = useCover();
const { frontUri, backUri } = storeToRefs(store);
</script>
<template>
<div class="flex flex-row grow">
<div class="grow p-4">
<h2 class="text-lg font-medium pb-4">Front</h2>
<iframe
:src="frontUri"
class="w-full aspect-A4 border border-slate-500 rounded"
/>
</div>
<div class="grow p-4">
<h2 class="text-lg font-medium pb-4">Back</h2>
<iframe
:src="backUri"
class="w-full aspect-A4 border border-slate-500 rounded"
/>
</div>
</div>
</template>

View File

@@ -0,0 +1,70 @@
<script setup lang="ts">
import Input from '@/components/form/Input.vue';
import TextArea from '@/components/form/TextArea.vue';
import Color from '@/components/form/Color.vue';
import SadFace from '@/icons/SadFace.vue';
import { randomLabel } from '@/lib/randomlabel';
import { useCover } from '@/stores/cover';
import { storeToRefs } from 'pinia';
import { ref } from 'vue';
const store = useCover();
const { customer, prefix, color, number } = storeToRefs(store);
const possibleLabels = [
'Hit it!',
'Let it rain!',
'💰💰💰',
'🤑🤑🤑',
'Make Rutte Proud',
'Kaching!',
'Rosebud',
'You show that customer',
'Do it for Berend',
];
const label = randomLabel(possibleLabels);
const renderError = ref<string | null>(null);
function doRender() {
try {
renderError.value = null;
store.render();
} catch (e) {
if (e instanceof Error) {
renderError.value = e.message;
} else if (typeof e === 'string') {
renderError.value = e;
} else {
renderError.value = 'An unknown error occurred';
console.error(e);
}
}
}
</script>
<template>
<div class="p-4 flex flex-col gap-4">
<h2 class="text-lg font-medium">Settings</h2>
<TextArea v-model="customer" label="Customer" />
<Input v-model="prefix" label="Prefix" />
<Input v-model="number" label="Number" />
<Color v-model="color" label="Highlight Color" />
<div
v-if="renderError !== null"
class="flex bg-red-100 rounded-lg p-4 mb-4 text-sm text-red-700 items-center gap-2"
role="alert"
>
<SadFace />
<div><span class="font-medium">Error:</span> {{ renderError }}</div>
</div>
<button
class="px-4 py-2 bg-blue-500 rounded-lg hover:bg-blue-600 text-white border-2 active:border-blue-500 focus:outline focus:outline-2 focus:outline-blue-500"
@click="label.update() && doRender()"
>
{{ label.label.value }}
</button>
</div>
</template>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
defineProps<{
label: string;
modelValue: string;
}>();
const emit = defineEmits<{
(event: 'update:modelValue', value: string): void;
}>();
</script>
<template>
<div>
<label class="block text-sm font-medium text-gray-700">{{ label }}</label>
<div class="mt-1 relative rounded-md shadow-sm">
<input
type="color"
class="block w-full py-2 px-4 h-10 bg-white border border-gray-300 hover:border-2 hover:border-blue-500 hover:cursor-pointer focus:border-blue-500 rounded-md"
:value="modelValue"
v-bind="$attrs"
@input="emit('update:modelValue', $event.target.value)"
/>
</div>
</div>
</template>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
defineProps<{
label: string;
modelValue: string;
}>();
const emit = defineEmits<{
(event: 'update:modelValue', value: string): void;
}>();
</script>
<template>
<div>
<label class="block text-sm font-medium text-gray-700">{{ label }}</label>
<div class="mt-1 relative rounded-md shadow-sm">
<input
type="text"
class="focus:ring-blue-500 focus:border-blue-500 block w-full border-gray-300 rounded-md"
:value="modelValue"
v-bind="$attrs"
@input="emit('update:modelValue', $event.target.value)"
/>
</div>
</div>
</template>

View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
defineProps<{
label: string;
modelValue: string;
}>();
const emit = defineEmits<{
(event: 'update:modelValue', value: string): void;
}>();
</script>
<template>
<div>
<label class="block text-sm font-medium text-gray-700">{{ label }}</label>
<div class="mt-1 relative rounded-md shadow-sm">
<textarea
class="focus:ring-blue-500 focus:border-blue-500 block w-full border-gray-300 rounded-md"
:value="modelValue"
v-bind="$attrs"
@input="emit('update:modelValue', $event.target.value)"
/>
</div>
</div>
</template>

View File

@@ -1,8 +1,8 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
import { DefineComponent } from 'vue';
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
const component: DefineComponent<{}, {}, any>;
export default component;
}

View File

@@ -0,0 +1,16 @@
<template>
<svg
class="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
</template>

View File

@@ -1,3 +1,14 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
html, body {
height: 100%;
}
#app {
min-height: 100%;
display: flex;
flex-direction: column;
justify-items: center;
}

View File

@@ -0,0 +1,51 @@
interface CoverArgs {
customer: string;
number: string;
numberPrefix?: string;
hlColor?: string;
}
interface CoverError {
error: string;
}
declare global {
interface Window {
generateCover(args: CoverArgs): Uint8Array | CoverError;
generateSplitCover(
args: CoverArgs
): { front: Uint8Array; back: Uint8Array } | CoverError;
}
}
const go = new Go();
WebAssembly.instantiateStreaming(fetch('covergen.wasm'), go.importObject).then(
(result) => {
go.run(result.instance);
}
);
export function generateCover(args: CoverArgs): File {
const result = window.generateCover(args);
if ('error' in result) {
throw result.error;
}
return new File([result], 'cover.pdf', { type: 'application/pdf' });
}
export function generateSplitCover(args: CoverArgs): {
front: File;
back: File;
} {
const result = window.generateSplitCover(args);
if ('error' in result) {
throw result.error;
}
return {
front: new File([result.front], 'front.pdf', { type: 'application/pdf' }),
back: new File([result.back], 'back.pdf', { type: 'application/pdf' }),
};
}

View File

@@ -0,0 +1,18 @@
import { readonly, ref } from 'vue';
export function randomLabel(options: string[]) {
const label = ref<string>();
const randomLabel = () => {
let newLabel;
do {
newLabel = options[Math.floor(Math.random() * options.length)];
} while (newLabel === label.value);
return newLabel;
};
label.value = randomLabel();
return {
label: readonly(label),
update: () => (label.value = randomLabel()),
};
}

View File

@@ -1,5 +1,7 @@
import { createApp } from 'vue'
import App from './App.vue'
import { createApp } from 'vue';
import App from './App.vue';
import './index.css';
import { createPinia } from 'pinia';
import './lib/covergen'; // Get that go app booting
createApp(App).mount('#app')
createApp(App).use(createPinia()).mount('#app');

View File

@@ -0,0 +1,28 @@
import { defineStore } from 'pinia';
import { generateSplitCover } from '@/lib/covergen';
export const useCover = defineStore('coverSettings', {
state() {
return {
customer: '',
prefix: 'offerte',
number: '',
color: '#ff00ff',
frontUri: '',
backUri: '',
};
},
actions: {
render() {
const { front, back } = generateSplitCover({
customer: this.customer,
numberPrefix: this.prefix,
number: this.number,
hlColor: this.color,
});
this.frontUri = URL.createObjectURL(front);
this.backUri = URL.createObjectURL(back);
},
},
});