Skip to content

Commit 3fe50d2

Browse files
committed
handle image upload and a few other small fixes
1 parent 8b1d709 commit 3fe50d2

File tree

6 files changed

+116
-46
lines changed

6 files changed

+116
-46
lines changed

src/components/AppImage.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
<template>
2-
<img v-bind="$attrs" />
2+
<img />
33
</template>
Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,63 @@
11
<script setup lang="ts">
2-
import { useBase64, useDropZone } from "@vueuse/core";
3-
import { ref } from "vue";
2+
import { useDropZone, useBase64 } from "@vueuse/core";
3+
import { ref, computed } from "vue";
4+
import use8baseStorage from "@/composables/use8baseStorage";
5+
const props = defineProps<{
6+
image?: string;
7+
loading?: boolean;
8+
}>();
9+
const emit = defineEmits<{
10+
(e: "upload", payload: { id: string }): void;
11+
}>();
12+
// data
13+
const image = ref<string | File | null | undefined>(props.image);
14+
const dropZoneRef = ref(null);
415
5-
const dropzoneEl = ref<HTMLElement | null>(null);
6-
const file = ref();
7-
const { base64: url } = useBase64(file);
8-
const { isOverDropZone } = useDropZone(dropzoneEl, (files) => {
9-
if (!files) return;
10-
file.value = files[0];
11-
console.log(file.value);
12-
});
16+
//@ts-expect-error is checked in src for string type
17+
const { base64 } = useBase64(image);
18+
const uploadingToFilestack = ref(false);
19+
const src = computed(() =>
20+
typeof image.value === "string" ? image.value : base64.value
21+
);
1322
14-
function onFileChange(e: any) {
15-
file.value = e.target.files[0];
16-
console.log(file.value);
23+
// functions
24+
function onFileSelect(e: Event) {
25+
handleFiles((e.target as HTMLInputElement).files);
1726
}
18-
19-
function reset() {
20-
file.value = null;
27+
function onDrop(files: File[] | null) {
28+
handleFiles(files);
29+
}
30+
const { uploadAsset } = use8baseStorage();
31+
async function handleFiles(files: FileList | File[] | null) {
32+
if (!files) return;
33+
image.value = files[0];
34+
uploadingToFilestack.value = true;
35+
const res = await uploadAsset(files[0]);
36+
emit("upload", res?.data.fileCreate);
37+
uploadingToFilestack.value = false;
2138
}
39+
const { isOverDropZone } = useDropZone(dropZoneRef, onDrop);
2240
</script>
2341

2442
<template>
2543
<div
26-
ref="dropzoneEl"
27-
class="flex m-4"
44+
class="bg-gray-100 p-2 flex justify-center items-center border-2 border-gray-100 relative"
2845
:class="{
29-
border: isOverDropZone,
30-
'bg-orange-100': isOverDropZone,
31-
'border-orange-400': isOverDropZone,
46+
'border-blue-200': isOverDropZone,
47+
'border-2': isOverDropZone,
3248
}"
33-
style="width: 300px; height: 200px; background: #3332; position: relative"
49+
ref="dropZoneRef"
3450
>
35-
<div class="m-auto opacity-50">Drop a image over or select</div>
36-
<div v-if="url" class="absolute left-0 top-0 bottom-0 right-0 object-cover">
37-
<img :src="url" class="h-full" />
38-
</div>
39-
<input
40-
class="absolute z-1 left-0 top-0 bottom-0 right-0 opacity-0"
41-
type="file"
42-
accept="image/*"
43-
@input="onFileChange"
44-
/>
51+
<label class="absolute top-0 left-0 right-0 bottom-0 block">
52+
<input
53+
accept="image/png, image/jpeg"
54+
class="hidden"
55+
type="file"
56+
@change="onFileSelect"
57+
/>
58+
</label>
59+
<AppImage v-if="image" :src="src" />
60+
<template v-else>{{ "Click or drop to upload image" }}</template>
61+
<AppLoader v-if="loading || uploadingToFilestack" :overlay="true" />
4562
</div>
46-
<button @click="reset">Reset</button>
4763
</template>

src/components/BoardMenu.vue

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,39 @@ import { Popup as KPopup } from "@progress/kendo-vue-popup";
33
import { Button as KButton } from "@progress/kendo-vue-buttons";
44
import { ref } from "vue";
55
import type { Board } from "@/types";
6+
import { onClickOutside } from "@vueuse/core";
7+
import { useMutation } from "@vue/apollo-composable";
8+
import attachImageToBoardMutation from "@/graphql/mutations/attachImageToBoard.mutation.gql";
9+
import { useAlerts } from "@/stores/alerts";
10+
11+
const alerts = useAlerts();
12+
613
const props = defineProps<{
714
board: Board;
815
}>();
16+
917
const show = ref(false);
1018
const menu = ref(null);
11-
defineEmits<{
19+
onClickOutside(menu, () => setTimeout(() => (show.value = false), 2));
20+
21+
const emit = defineEmits<{
1222
(e: "deleteBoard", payload: null): void;
23+
(e: "imageUpload", payload: { id: string }): void;
1324
}>();
25+
26+
const {
27+
mutate: attachImageToBoard,
28+
onError: errorAttachingImage,
29+
onDone: onImageAttached,
30+
loading: imageLoading,
31+
} = useMutation(attachImageToBoardMutation);
32+
errorAttachingImage((error) => {
33+
console.log(error);
34+
alerts.error("Error setting board image");
35+
});
36+
onImageAttached((result) => {
37+
emit("imageUpload", result.data.boardUpdate.image);
38+
});
1439
</script>
1540
<template>
1641
<div>
@@ -42,6 +67,20 @@ defineEmits<{
4267
Delete Board
4368
</button>
4469
</li>
70+
<li>
71+
<strong>Board Image</strong>
72+
<AppImageDropzone
73+
class="aspect-video w-56"
74+
:image="board.image?.downloadUrl"
75+
:loading="imageLoading"
76+
@upload="
77+
attachImageToBoard({
78+
id: board.id,
79+
imageId: $event.id,
80+
})
81+
"
82+
/>
83+
</li>
4584
</ul>
4685
</div>
4786
</KPopup>

src/components/TheDrawer.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const items = computed(() => [
4646
4747
function onSelect({ itemIndex }: { itemIndex: number }) {
4848
const item = items.value[itemIndex];
49+
if (!item) return;
4950
if (item.data.path) router.push(item.data.path);
5051
if (typeof item.data.action === "function") item.data.action();
5152
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
mutation attachImageToBoard($id: ID!, $imageId: ID!) {
2+
boardUpdate(
3+
filter: { id: $id }
4+
data: { image: { reconnect: { id: $imageId } } }
5+
) {
6+
id
7+
image {
8+
id
9+
downloadUrl
10+
}
11+
}
12+
}

src/pages/boards/index.vue

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import boardsQuery from "@/graphql/queries/boards.query.gql";
44
import createBoardMutation from "@/graphql/mutations/createBoard.mutation.gql";
55
import { useMutation, useQuery } from "@vue/apollo-composable";
66
import { computed } from "vue";
7+
import router from "@/router";
78
89
const { result, loading, onError } = useQuery(boardsQuery);
910
const boards = computed(() => result.value?.boardsList?.items || []);
@@ -22,17 +23,18 @@ const { mutate: createBoard } = useMutation(createBoardMutation, () => ({
2223
},
2324
}));
2425
25-
// function createBoard() {
26-
// alerts.success("Board created!");
27-
// }
26+
async function handleBoardCreate() {
27+
const newBoardPayload = {
28+
data: {
29+
title: "My New Board",
30+
},
31+
};
32+
const res = await createBoard(newBoardPayload);
33+
alerts.success("New Board created!");
34+
router.push(`/boards/${res?.data.boardCreate.id}`);
35+
}
2836
29-
const newBoardPayload = {
30-
data: {
31-
title: "Test board 2",
32-
},
33-
};
34-
35-
const getCoolGradient = (index) => {
37+
const getCoolGradient = (index: number) => {
3638
let finalGradientString = "";
3739
switch (index) {
3840
case 1:
@@ -65,7 +67,7 @@ const getCoolGradient = (index) => {
6567
class="transition duration-100 ease-in border rounded-md hover:-rotate-3"
6668
/>
6769
</div>
68-
<button class="text-gray-500" @click="createBoard(newBoardPayload)">
70+
<button class="text-gray-500" @click="handleBoardCreate">
6971
<span>New Board +</span>
7072
</button>
7173
</div>

0 commit comments

Comments
 (0)