Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 73 additions & 9 deletions app/src/components/BaseImageUpload.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,66 @@
<template>
<div :class="width">
<label class="flex flex-col bg-white items-center block px-6 py-8 border border-grey-400 cursor-pointer">
<span>Choose image file to upload</span>
<div v-if="isValid && val" :id="`${id}-success`">Uploaded: {{ val.name }}</div>
<input type="file" @input="onInput" class="hidden" />
</label>
<div v-if="!isValid" class="bg-pink p-4 text-white" :id="`${id}-error`">{{ errorMsg }}</div>
<template v-if="isUploading">
<span>Uploading to IPFS...</span>
</template>

<!-- Image Upload -->
<template v-else-if="!logoURI || !isValid">
<div class="block">
<label class="w-100 flex flex-col bg-white items-center px-6 py-8 border border-grey-400 cursor-pointer">
<input type="file" @input="onInput" class="hidden" />
<div class="flex py-12 px-0 md:px-12 gap-x-12">
<div
class="
group
flex
items-center
justify-center
h-24
border border-grey-400
p-12
cursor-pointer
hover:border-grey-500
"
>
<ImportIcon class="icon icon-small icon-primary" />
</div>
<div class="text-grey-400 flex items-center">
{{ errorMsg }}
</div>
</div>
</label>
</div>

<div v-if="logoURI && !isValid" class="bg-pink p-4 text-white" :id="`${id}-error`">
{{ errorMsg }}
</div>
</template>

<!-- Uploaded Image Preview -->
<div v-else-if="logoURI" class="py-8">
<div class="grid grid-cols-12 items-center gap-x-4 md:gap-x-8">
<!-- image -->
<div class="col-span-5 md:col-span-2 mb-3 md:mb-0">
<img class="shadow-light" :src="logoURI" />
</div>

<!-- filename -->
<div class="col-span-6 md:col-span-4 mb-3 md:mb-0 truncate">
{{ logoURI }}
</div>

<!-- delete icon -->
<div class="col-span-1 md:col-span-3 mb-3 md:mb-0 justify-self-end">
<XIcon class="icon icon-primary icon-small cursor-pointer" @click="removeLogo" />
</div>
</div>
</div>
</template>

<script lang="ts">
import { defineComponent, ref, watch } from 'vue';
import { ImportIcon } from '@fusion-icons/vue/interface';
import { CloseIcon as XIcon } from '@fusion-icons/vue/interface';

export default defineComponent({
name: 'BaseImageUpload',
Expand All @@ -23,7 +73,9 @@ export default defineComponent({
id: { type: String, required: false, default: undefined }, // id, for accessibility
required: { type: Boolean, required: false, default: true }, // is required
width: { type: String, required: false, default: 'w-full' }, // input field width
showBorder: { type: Boolean, requred: false, default: true }, // show border below input
showBorder: { type: Boolean, required: false, default: true }, // show border below input
logoURI: { type: String, required: false, default: undefined }, // URI of uploaded image
isUploading: { type: Boolean, required: false, default: false },
rules: {
// Validation rules, as a function that takes one input and returns a bool
type: Function,
Expand All @@ -32,6 +84,8 @@ export default defineComponent({
},
},

components: { ImportIcon, XIcon },

setup(props, context) {
const val = ref<File | undefined>(props.modelValue as File);
const isValid = ref(true);
Expand All @@ -49,7 +103,17 @@ export default defineComponent({
val.value = file;
context.emit('update:modelValue', file); // https://v3.vuejs.org/guide/migration/v-model.html#v-model
}
return { onInput, isValid, val };

function removeLogo() {
context.emit('update:modelValue', undefined);
}

return {
onInput,
removeLogo,
isValid,
val,
};
},
});
</script>
75 changes: 75 additions & 0 deletions app/src/components/ImageController.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<template>
<InputRow v-for="(item, index) in controllerList" :key="item.id">
<template v-slot:label>Image ({{ index + 1 }}):</template>
<template v-slot:input>
<ImageRow :index="index" @discard="discardImage" :imageController="item" :desiredRatio="desiredRatio" />
</template>
</InputRow>

<InputRow v-if="controllerList.length < limit">
<template v-slot:label>{{ title }}:</template>
<template v-slot:input>
<ImageDropZone v-on:addImage="addImage" />
</template>
</InputRow>
</template>

<script lang="ts">
import { defineComponent, PropType, ref } from 'vue';

import { ImageEditorVariant, ControlledImage } from '../types/images';

import ImageRow from './ImageRow.vue';
import ImageDropZone from './ImageDropZone.vue';
import InputRow from './InputRow.vue';

export default defineComponent({
name: 'ImageController',
props: {
multi: {
type: Boolean,
default: false,
},
limit: {
type: Number,
default: 1,
},
maxSize: {
type: Number,
default: 500 * 1024,
},
variant: {
type: String as PropType<ImageEditorVariant>,
default: 'RECTANGLE',
},
desiredRatio: {
type: Number,
default: 16 / 9,
},
title: {
type: String,
required: true,
},
},
components: {
ImageRow,
ImageDropZone,
InputRow,
},
setup(props) {
const controllerList = ref<Array<ControlledImage>>([]);

const addImage = (image: File) => {
const controlledImage = new ControlledImage(image, props.desiredRatio);

controllerList.value.push(controlledImage);
};

const discardImage = (id: number) => {
controllerList.value = controllerList.value.filter((item) => item.id !== id);
};

return { addImage, controllerList, discardImage };
},
});
</script>
82 changes: 82 additions & 0 deletions app/src/components/ImageDropZone.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<template>
<div :class="width">
<label
v-on:dragover="fileDragOver($event)"
v-on:drop="fileDrop($event)"
class="flex flex-col bg-white items-center block px-6 py-8 border border-grey-400 cursor-pointer"
>
<span>Choose or Drop an image to upload</span>
<input type="file" @input="onInput" class="hidden" />
</label>
<div v-if="!isValid" class="bg-pink p-4 text-white" :id="`${id}-error`">{{ errorMsg }}</div>
</div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
name: 'ImageDropZone',

props: {
// --- Required props ---
// Getting the prop type right as File | undefined was a pain, so just leaving out the type definition here
modelValue: { required: true }, // from v-model, don't pass this directly
addImage: { required: true },
// --- Optional props ---
errorMsg: { type: String, required: false, default: undefined }, // message to show on error
id: { type: String, required: false, default: undefined }, // id, for accessibility
required: { type: Boolean, required: false, default: true }, // is required
width: { type: String, required: false, default: 'w-full' }, // input field width
showBorder: { type: Boolean, requred: false, default: true }, // show border below input
rules: {
// Validation rules, as a function that takes one input and returns a bool
type: Function,
required: false,
default: () => true,
},
},
components: {},

setup(_, context) {
const isValid = ref(true);
const selectedImage = ref<string | null>(null);
const mimeType = ref('');
const finalImage = ref('');

const handleFile = (file: File) => {
context.emit('addImage', file); // https://v3.vuejs.org/guide/migration/v-model.html#v-model

mimeType.value = 'not-for-now'; //file.type;

const reader = new FileReader();
reader.onload = (e) => {
if (e.target !== null) {
selectedImage.value = String(e.target.result);
}
};
reader.readAsDataURL(file);
};

const fileDragOver = (e: Event) => {
e.preventDefault();
};
const fileDrop = (e: DragEvent) => {
e.preventDefault();

if (e.dataTransfer !== null) {
const file: File = (e.dataTransfer.files as FileList)[0];
handleFile(file);
}
};

function onInput(e: Event) {
const target = e.target as HTMLInputElement;
const file: File = (target.files as FileList)[0];
handleFile(file);
}

return { onInput, isValid, fileDragOver, fileDrop, selectedImage, mimeType, finalImage };
},
});
</script>
Loading