forked from trent_larson/crowd-funder-for-time-pwa
add file-chooser to the profile image selection
This commit is contained in:
@@ -34,7 +34,7 @@
|
|||||||
backgroundColor: '#f8f8f8',
|
backgroundColor: '#f8f8f8',
|
||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
}"
|
}"
|
||||||
:img="URL.createObjectURL(blob)"
|
:img="createBlobURL(blob)"
|
||||||
:options="{
|
:options="{
|
||||||
viewMode: 1,
|
viewMode: 1,
|
||||||
dragMode: 'crop',
|
dragMode: 'crop',
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<img :src="URL.createObjectURL(blob)" class="mt-2 rounded" />
|
<img :src="createBlobURL(blob)" class="mt-2 rounded" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute bottom-[1rem] left-[1rem] px-2 py-1">
|
<div class="absolute bottom-[1rem] left-[1rem] px-2 py-1">
|
||||||
@@ -60,7 +60,10 @@
|
|||||||
<span>Upload</span>
|
<span>Upload</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute bottom-[1rem] right-[1rem] px-2 py-1">
|
<div
|
||||||
|
v-if="showRetry"
|
||||||
|
class="absolute bottom-[1rem] right-[1rem] px-2 py-1"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
@click="retryImage"
|
@click="retryImage"
|
||||||
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white py-1 px-2 rounded-md"
|
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white py-1 px-2 rounded-md"
|
||||||
@@ -127,17 +130,19 @@ import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
|||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
|
|
||||||
@Component({ components: { Camera, VuePictureCropper } })
|
@Component({ components: { Camera, VuePictureCropper } })
|
||||||
export default class GiftedPhotoDialog extends Vue {
|
export default class PhotoDialog extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
activeDeviceNumber = 0;
|
activeDeviceNumber = 0;
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
blob: Blob | null = null;
|
blob?: Blob;
|
||||||
claimType = "GiveAction";
|
claimType = "GiveAction";
|
||||||
crop = false;
|
crop = false;
|
||||||
|
fileName?: string;
|
||||||
mirror = false;
|
mirror = false;
|
||||||
numDevices = 0;
|
numDevices = 0;
|
||||||
setImage: (arg: string) => void = () => {};
|
setImageCallback: (arg: string) => void = () => {};
|
||||||
|
showRetry = true;
|
||||||
uploading = false;
|
uploading = false;
|
||||||
visible = false;
|
visible = false;
|
||||||
|
|
||||||
@@ -163,7 +168,13 @@ export default class GiftedPhotoDialog extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open(setImageFn: (arg: string) => void, crop?: boolean, claimType?: string) {
|
open(
|
||||||
|
setImageFn: (arg: string) => void,
|
||||||
|
crop?: boolean,
|
||||||
|
claimType?: string,
|
||||||
|
blob?: Blob, // for image upload, just to use the cropping function
|
||||||
|
inputFileName?: string,
|
||||||
|
) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.crop = !!crop;
|
this.crop = !!crop;
|
||||||
this.claimType = claimType || "GiveAction";
|
this.claimType = claimType || "GiveAction";
|
||||||
@@ -171,7 +182,16 @@ export default class GiftedPhotoDialog extends Vue {
|
|||||||
if (bottomNav) {
|
if (bottomNav) {
|
||||||
bottomNav.style.display = "none";
|
bottomNav.style.display = "none";
|
||||||
}
|
}
|
||||||
this.setImage = setImageFn;
|
this.setImageCallback = setImageFn;
|
||||||
|
if (blob) {
|
||||||
|
this.blob = blob;
|
||||||
|
this.fileName = inputFileName;
|
||||||
|
this.showRetry = false;
|
||||||
|
} else {
|
||||||
|
this.blob = undefined;
|
||||||
|
this.fileName = undefined;
|
||||||
|
this.showRetry = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
@@ -180,7 +200,7 @@ export default class GiftedPhotoDialog extends Vue {
|
|||||||
if (bottomNav) {
|
if (bottomNav) {
|
||||||
bottomNav.style.display = "";
|
bottomNav.style.display = "";
|
||||||
}
|
}
|
||||||
this.blob = null;
|
this.blob = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async cameraStarted() {
|
async cameraStarted() {
|
||||||
@@ -236,10 +256,13 @@ export default class GiftedPhotoDialog extends Vue {
|
|||||||
|
|
||||||
// The resolution is only necessary because of that mobile portrait-orientation case.
|
// The resolution is only necessary because of that mobile portrait-orientation case.
|
||||||
// The mobile emulation in a browser shows something stretched vertically, but real devices work fine.
|
// The mobile emulation in a browser shows something stretched vertically, but real devices work fine.
|
||||||
this.blob = await cameraComponent?.snapshot({
|
this.blob =
|
||||||
height: imageHeight,
|
(await cameraComponent?.snapshot({
|
||||||
width: imageWidth,
|
height: imageHeight,
|
||||||
}); // png is default; if that changes, change extension in formData.append
|
width: imageWidth,
|
||||||
|
})) || undefined;
|
||||||
|
// png is default
|
||||||
|
this.fileName = "snapshot.png";
|
||||||
if (!this.blob) {
|
if (!this.blob) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
@@ -254,8 +277,13 @@ export default class GiftedPhotoDialog extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createBlobURL(blob: Blob): string {
|
||||||
|
console.log("blob", blob);
|
||||||
|
return URL.createObjectURL(blob);
|
||||||
|
}
|
||||||
|
|
||||||
async retryImage() {
|
async retryImage() {
|
||||||
this.blob = null;
|
this.blob = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/****
|
/****
|
||||||
@@ -307,7 +335,7 @@ export default class GiftedPhotoDialog extends Vue {
|
|||||||
this.uploading = true;
|
this.uploading = true;
|
||||||
|
|
||||||
if (this.crop) {
|
if (this.crop) {
|
||||||
this.blob = await cropper?.getBlob();
|
this.blob = (await cropper?.getBlob()) || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const identifier = await getIdentity(this.activeDid);
|
const identifier = await getIdentity(this.activeDid);
|
||||||
@@ -330,7 +358,7 @@ export default class GiftedPhotoDialog extends Vue {
|
|||||||
this.uploading = false;
|
this.uploading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
formData.append("image", this.blob, "snapshot.png");
|
formData.append("image", this.blob, this.fileName || "snapshot.png");
|
||||||
formData.append("claimType", this.claimType);
|
formData.append("claimType", this.claimType);
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
@@ -341,8 +369,8 @@ export default class GiftedPhotoDialog extends Vue {
|
|||||||
this.uploading = false;
|
this.uploading = false;
|
||||||
|
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
this.blob = null;
|
this.blob = undefined;
|
||||||
this.setImage(response.data.url as string);
|
this.setImageCallback(response.data.url as string);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error uploading the image", error);
|
console.error("Error uploading the image", error);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -355,7 +383,7 @@ export default class GiftedPhotoDialog extends Vue {
|
|||||||
5000,
|
5000,
|
||||||
);
|
);
|
||||||
this.uploading = false;
|
this.uploading = false;
|
||||||
this.blob = null;
|
this.blob = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,19 +86,25 @@
|
|||||||
class="text-red-500 fa-fw ml-8 mt-8 w-12 h-12"
|
class="text-red-500 fa-fw ml-8 mt-8 w-12 h-12"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<div v-else class="text-center">
|
||||||
<fa
|
<div class>
|
||||||
icon="camera"
|
<fa
|
||||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-md"
|
icon="camera"
|
||||||
@click="openPhotoDialog"
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-md"
|
||||||
/>
|
@click="openPhotoDialog(undefined, undefined)"
|
||||||
</span>
|
/>
|
||||||
<GiftedPhotoDialog ref="photoDialog" />
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="file" @change="uploadPhotoFile" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PhotoDialog ref="photoDialog" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-6">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center text-center">
|
||||||
... and those without your image see this (if you let them see your
|
People without your image see this:
|
||||||
activity):
|
<br />
|
||||||
|
(if you've let them see your activity)
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
@@ -577,11 +583,11 @@
|
|||||||
|
|
||||||
<div class="ml-4 mt-2">
|
<div class="ml-4 mt-2">
|
||||||
Import
|
Import
|
||||||
<input type="file" @change="uploadFile" class="ml-2" />
|
<input type="file" @change="uploadImportFile" class="ml-2" />
|
||||||
<div v-if="showContactImport()">
|
<div v-if="showContactImport()">
|
||||||
<button
|
<button
|
||||||
class="block text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
|
class="block text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
|
||||||
@click="confirmSubmitFile()"
|
@click="confirmSubmitImportFile()"
|
||||||
>
|
>
|
||||||
Import Settings & Contacts
|
Import Settings & Contacts
|
||||||
<br />
|
<br />
|
||||||
@@ -614,7 +620,7 @@ import { ref } from "vue";
|
|||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
|
||||||
import GiftedPhotoDialog from "@/components/GiftedPhotoDialog.vue";
|
import PhotoDialog from "@/components/PhotoDialog.vue";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import TopMessage from "@/components/TopMessage.vue";
|
import TopMessage from "@/components/TopMessage.vue";
|
||||||
import {
|
import {
|
||||||
@@ -645,10 +651,11 @@ interface IAccount {
|
|||||||
derivationPath: string;
|
derivationPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputFileNameRef = ref<Blob>();
|
const inputImportFileNameRef = ref<Blob>();
|
||||||
|
const inputPhotoFileNameRef = ref<Blob>();
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { EntityIcon, GiftedPhotoDialog, QuickNav, TopMessage },
|
components: { EntityIcon, PhotoDialog, QuickNav, TopMessage },
|
||||||
})
|
})
|
||||||
export default class AccountViewView extends Vue {
|
export default class AccountViewView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
@@ -1120,16 +1127,36 @@ export default class AccountViewView extends Vue {
|
|||||||
console.error("Export Error:", error);
|
console.error("Export Error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadFile(event: Event) {
|
async uploadPhotoFile(event: Event) {
|
||||||
inputFileNameRef.value = event.target.files[0];
|
inputPhotoFileNameRef.value = event.target.files[0];
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/File
|
||||||
|
// ... plus it has a `type` property from my testing
|
||||||
|
const file = inputPhotoFileNameRef.value;
|
||||||
|
if (file != null) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = async (e) => {
|
||||||
|
const data = e.target?.result as ArrayBuffer;
|
||||||
|
if (data) {
|
||||||
|
const blob = new Blob([new Uint8Array(data)], {
|
||||||
|
type: file.type,
|
||||||
|
});
|
||||||
|
this.openPhotoDialog(blob, file.name as string);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(file as Blob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadImportFile(event: Event) {
|
||||||
|
inputImportFileNameRef.value = event.target.files[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
showContactImport() {
|
showContactImport() {
|
||||||
return !!inputFileNameRef.value;
|
return !!inputImportFileNameRef.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmSubmitFile() {
|
confirmSubmitImportFile() {
|
||||||
if (inputFileNameRef.value != null) {
|
if (inputImportFileNameRef.value != null) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "modal",
|
group: "modal",
|
||||||
@@ -1138,7 +1165,7 @@ export default class AccountViewView extends Vue {
|
|||||||
text:
|
text:
|
||||||
"This will replace all settings and contacts, so we recommend you first do the backup step above." +
|
"This will replace all settings and contacts, so we recommend you first do the backup step above." +
|
||||||
" Are you sure you want to import and replace all contacts and settings?",
|
" Are you sure you want to import and replace all contacts and settings?",
|
||||||
onYes: this.submitFile,
|
onYes: this.submitImportFile,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@@ -1150,10 +1177,10 @@ export default class AccountViewView extends Vue {
|
|||||||
*
|
*
|
||||||
* @throws Will notify the user if there is an export error.
|
* @throws Will notify the user if there is an export error.
|
||||||
*/
|
*/
|
||||||
async submitFile() {
|
async submitImportFile() {
|
||||||
if (inputFileNameRef.value != null) {
|
if (inputImportFileNameRef.value != null) {
|
||||||
await db.delete();
|
await db.delete();
|
||||||
await Dexie.import(inputFileNameRef.value as Blob, {
|
await Dexie.import(inputImportFileNameRef.value as Blob, {
|
||||||
progressCallback: this.progressCallback,
|
progressCallback: this.progressCallback,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1366,8 +1393,8 @@ export default class AccountViewView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
openPhotoDialog() {
|
openPhotoDialog(blob?: Blob, fileName?: string) {
|
||||||
(this.$refs.photoDialog as GiftedPhotoDialog).open(
|
(this.$refs.photoDialog as PhotoDialog).open(
|
||||||
async (imgUrl) => {
|
async (imgUrl) => {
|
||||||
await db.open();
|
await db.open();
|
||||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
@@ -1378,6 +1405,8 @@ export default class AccountViewView extends Vue {
|
|||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
IMAGE_TYPE_PROFILE,
|
IMAGE_TYPE_PROFILE,
|
||||||
|
blob,
|
||||||
|
fileName,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<GiftedPhotoDialog ref="photoDialog" />
|
<PhotoDialog ref="photoDialog" />
|
||||||
|
|
||||||
<div v-if="projectId" class="mt-4">
|
<div v-if="projectId" class="mt-4">
|
||||||
<fa
|
<fa
|
||||||
@@ -130,11 +130,11 @@ import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
|||||||
import { createAndSubmitGive, getPlanFromCache } from "@/libs/endorserServer";
|
import { createAndSubmitGive, getPlanFromCache } from "@/libs/endorserServer";
|
||||||
import * as libsUtil from "@/libs/util";
|
import * as libsUtil from "@/libs/util";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
import GiftedPhotoDialog from "@/components/GiftedPhotoDialog.vue";
|
import PhotoDialog from "@/components/PhotoDialog.vue";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
GiftedPhotoDialog,
|
PhotoDialog,
|
||||||
QuickNav,
|
QuickNav,
|
||||||
TopMessage,
|
TopMessage,
|
||||||
},
|
},
|
||||||
@@ -280,7 +280,7 @@ export default class GiftedDetails extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openPhotoDialog() {
|
openPhotoDialog() {
|
||||||
(this.$refs.photoDialog as GiftedPhotoDialog).open((imgUrl) => {
|
(this.$refs.photoDialog as PhotoDialog).open((imgUrl) => {
|
||||||
this.imageUrl = imgUrl;
|
this.imageUrl = imgUrl;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user