Compare commits

4 Commits

Author SHA1 Message Date
b59c939e3e Add acl
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-20 21:22:52 +01:00
3a30c7a6f4 Actually fix path
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-20 21:18:57 +01:00
bdab11b100 try fix path
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-20 21:16:30 +01:00
d3f6f0e946 update ci
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-20 21:13:40 +01:00
33 changed files with 100 additions and 4288 deletions

View File

@@ -3,10 +3,6 @@ kind: pipeline
type: kubernetes
name: build-clis
#trigger:
# branch:
# - main
steps:
- name: build
image: golang:1.17
@@ -19,6 +15,7 @@ steps:
source: dist/*
target: /dist/
strip_prefix: dist/
acl: readonly
path_style: true
endpoint: https://s3.blacknova.io
@@ -32,46 +29,22 @@ kind: pipeline
type: kubernetes
name: build-wasm
trigger:
branch:
- main
volumes:
- name: wasm
temp: {}
steps:
- name: build wasm
- name: build
image: golang:1.17
environment:
GOOS: js
GOARCH: wasm
volumes:
- name: wasm
path: /wasm
commands:
- make wasm
- "cp covergen.wasm /wasm/covergen.wasm"
# Grab the wasm shim from the docker image, otherwise there is a mismatch
- "cp /usr/local/go/misc/wasm/wasm_exec.js /wasm/wasm_exec.js"
- name: build frontend
image: node:16
volumes:
- name: wasm
path: /wasm
commands:
- cd frontend
- yarn install
- "cp /wasm/* public/"
- yarn build
- name: upload
image: plugins/s3
settings:
bucket: covergen
source: frontend/dist/**/*
source: assets/**/*
target: /
strip_prefix: frontend/dist/
strip_prefix: assets/
acl: readonly
path_style: true
endpoint: https://s3.blacknova.io

2
.gitignore vendored
View File

@@ -1,5 +1,5 @@
covergen
!covergen/
*.pdf
*.wasm
assets/*.wasm
dist/

View File

@@ -6,4 +6,4 @@ build-cross-clis:
.PHONY: wasm
wasm:
GOOS=js GOARCH=wasm go build -o covergen.wasm ./cmd/wasm/main.go
GOOS=js GOARCH=wasm go build -o assets/covergen.wasm ./cmd/wasm/main.go

View File

@@ -1,20 +0,0 @@
# Covergen
Just generates some magic PDF invoice covers using straight up dark magic.
## Just be lazy
Go see it in action and download it from [https://s3.blacknova.io/covergen/index.html](https://s3.blacknova.io/covergen/index.html)
## Just build it
```
$ go build -o covergen ./cmd/covergen
```
## It has wasm!
```
$ make wasm
```

93
assets/index.html Normal file
View File

@@ -0,0 +1,93 @@
<html>
<head>
<meta charset="utf-8" />
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("covergen.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
function makeCover(args) {
const result = window.generateCover(args);
if (result.error) {
throw result.error;
}
return new File([result], 'cover.pdf', {type: 'application/pdf'});
}
function makeSplitCover(args) {
const result = window.generateSplitCover(args);
if (result.error) {
throw result.error;
}
return Object.fromEntries(['front', 'back'].map((side) => [side, new File([result[side]], `${side}.pdf`, {type: 'application/pdf'})]));
}
function letsfuckinggo() {
const covers = makeSplitCover({
customer: document.getElementById('customer').value,
number: document.getElementById('number').value,
numberPrefix: document.getElementById('prefix').value,
hlColor: document.getElementById('color').value,
})
document.getElementById('front').src = window.URL.createObjectURL(covers.front);
document.getElementById('back').src = window.URL.createObjectURL(covers.back);
}
</script>
<style>
html, body {
height: 100%;
width: 100%;
}
.covers {
height: 100%;
width: 90%;
display: flex;
flex-direction: row;
}
.covers iframe {
flex-grow: 1;
}
</style>
</head>
<body>
<div>
<label>
Customer:
<textarea id="customer"></textarea>
</label>
</div>
<div>
<label>
Prefix:
<input id="prefix" type="text" value="offerte">
</label>
</div>
<div>
<label>
Number:
<input id="number" type="text">
</label>
</div>
<div>
<label>
Color:
<input id="color" type="color" value="#FF69B4">
</label>
</div>
<div>
<button onclick="letsfuckinggo()">Fuck it!</button>
</div>
<div class="covers">
<iframe id="front"></iframe>
<iframe id="back"></iframe>
</div>
</body>
</html>

View File

@@ -1,12 +0,0 @@
{
"extends": [
"plugin:vue/vue3-recommended",
"plugin:prettier-vue/recommended",
"@vue/typescript/recommended",
"prettier"
],
"rules": {
"@typescript-eslint/explicit-module-boundary-types": [0],
"vue/multi-word-component-names": 0
}
}

8
frontend/.gitignore vendored
View File

@@ -1,8 +0,0 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
.yarn/install-state.gz
public/*.wasm

View File

@@ -1,3 +0,0 @@
{
"singleQuote": true
}

View File

@@ -1,11 +0,0 @@
# Vue 3 + Typescript + Vite
This template should help get you started developing with Vue 3 and Typescript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's `.vue` type support plugin by running `Volar: Switch TS Plugin on/off` from VSCode command palette.

View File

@@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
<title>NVLS | Covergen</title>
</head>
<body class="bg-gray-200">
<div id="app"></div>
<script src="/wasm_exec.js"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -1,37 +0,0 @@
{
"name": "frontend",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"lint": "yarn lint:ts && yarn lint:style",
"lint:ts": "vue-tsc --noEmit",
"lint:style": "eslint --ext .ts,.vue --ignore-path .gitignore .",
"lint:fix": "yarn lint:style --fix"
},
"dependencies": {
"@tailwindcss/forms": "^0.4.0",
"pinia": "^2.0.9",
"tailwindcss": "^3.0.15",
"vue": "^3.2.25"
},
"devDependencies": {
"@types/golang-wasm-exec": "^1.15.0",
"@types/node": "^17.0.10",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"@vitejs/plugin-vue": "^2.0.0",
"@vue/eslint-config-typescript": "^10.0.0",
"autoprefixer": "^10.4.2",
"eslint": "^8.7.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier-vue": "^3.1.0",
"eslint-plugin-vue": "^8.3.0",
"postcss": "^8.4.5",
"prettier": "^2.5.1",
"typescript": "^4.4.4",
"vite": "^2.7.2",
"vue-tsc": "^0.29.8"
}
}

View File

@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,18 +0,0 @@
<script setup lang="ts">
import Nav from './components/Nav.vue';
import Settings from './components/Settings.vue';
import Renderer from './components/Renderer.vue';
</script>
<template>
<Nav />
<div class="flex grow space-x-8">
<div class="w-80">
<Settings />
</div>
<Renderer />
</div>
</template>
<style scoped>
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -1,35 +0,0 @@
<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

@@ -1,25 +0,0 @@
<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

@@ -1,70 +0,0 @@
<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

@@ -1,30 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
label: string;
modelValue: string;
}>();
const emit = defineEmits<{
(event: 'update:modelValue', value: string): void;
}>();
const localValue = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value),
});
</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
v-model="localValue"
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"
v-bind="$attrs"
/>
</div>
</div>
</template>

View File

@@ -1,30 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
label: string;
modelValue: string;
}>();
const emit = defineEmits<{
(event: 'update:modelValue', value: string): void;
}>();
const localValue = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value),
});
</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
v-model="localValue"
type="text"
class="focus:ring-blue-500 focus:border-blue-500 block w-full border-gray-300 rounded-md"
v-bind="$attrs"
/>
</div>
</div>
</template>

View File

@@ -1,29 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
label: string;
modelValue: string;
}>();
const emit = defineEmits<{
(event: 'update:modelValue', value: string): void;
}>();
const localValue = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value),
});
</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
v-model="localValue"
class="focus:ring-blue-500 focus:border-blue-500 block w-full border-gray-300 rounded-md"
v-bind="$attrs"
/>
</div>
</div>
</template>

View File

@@ -1,8 +0,0 @@
/// <reference types="vite/client" />
declare module '*.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;
}

View File

@@ -1,16 +0,0 @@
<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,14 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
html, body {
height: 100%;
}
#app {
min-height: 100%;
display: flex;
flex-direction: column;
justify-items: center;
}

View File

@@ -1,51 +0,0 @@
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

@@ -1,18 +0,0 @@
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,7 +0,0 @@
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).use(createPinia()).mount('#app');

View File

@@ -1,28 +0,0 @@
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);
},
},
});

View File

@@ -1,21 +0,0 @@
const defaultTheme = require('tailwindcss/defaultTheme');
module.exports = {
content: [
"./index.html",
"./src/**/*.{vue,js,ts}",
],
theme: {
extend: {
fontFamily: {
sans: ['Inter var', ...defaultTheme.fontFamily.sans],
},
aspectRatio: {
'A4': '1 / 1.4142',
}
},
},
plugins: [
require('@tailwindcss/forms'),
],
}

View File

@@ -1,18 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}

View File

@@ -1,13 +0,0 @@
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, './src'),
},
},
});

File diff suppressed because it is too large Load Diff