|
|
@ -1,3 +1,40 @@ |
|
|
|
<!-- |
|
|
|
SharedPhotoView.vue - External Image Sharing Handler |
|
|
|
|
|
|
|
Handles images shared to TimeSafari from external applications via deep linking. |
|
|
|
Provides options to use shared images for gifts or profile pictures, with |
|
|
|
integrated image upload and processing capabilities. |
|
|
|
|
|
|
|
Key Features: |
|
|
|
- Process externally shared images from device |
|
|
|
- Upload images to image server with authentication |
|
|
|
- Convert images to gifts (GiveAction) or profile pictures |
|
|
|
- Temporary storage management for shared image data |
|
|
|
- Comprehensive error handling for upload failures |
|
|
|
- Integration with PhotoDialog for profile image cropping |
|
|
|
|
|
|
|
Image Flow: |
|
|
|
1. External app shares image → TimeSafari deep link |
|
|
|
2. Image stored temporarily in database as base64 |
|
|
|
3. User chooses: Record Gift, Save as Profile, or Cancel |
|
|
|
4. Image uploaded to server with JWT authentication |
|
|
|
5. Temporary storage cleaned up |
|
|
|
|
|
|
|
Navigation Paths: |
|
|
|
- External Share → SharedPhotoView |
|
|
|
- Record Gift → GiftedDetailsView (with image URL) |
|
|
|
- Save Profile → PhotoDialog → AccountView |
|
|
|
- Cancel → HomeView |
|
|
|
|
|
|
|
Migration Status: ✅ Complete Enhanced Triple Migration Pattern |
|
|
|
- Phase 1: Database Migration (PlatformServiceMixin) ✅ |
|
|
|
- Phase 2: SQL Abstraction ($dbQuery, $dbExec, $accountSettings, $updateSettings) ✅ |
|
|
|
- Phase 3: Notification Migration (3 constants, helper methods) ✅ |
|
|
|
- Phase 4: Template Streamlining (Simple template) ✅ |
|
|
|
|
|
|
|
Author: Matthew Raymer |
|
|
|
Last Updated: 2025-07-07 |
|
|
|
--> |
|
|
|
<template> |
|
|
|
<QuickNav /> |
|
|
|
<!-- CONTENT --> |
|
|
@ -79,43 +116,77 @@ import { |
|
|
|
IMAGE_TYPE_PROFILE, |
|
|
|
NotificationIface, |
|
|
|
} from "../constants/app"; |
|
|
|
import * as databaseUtil from "../db/databaseUtil"; |
|
|
|
import { accessToken } from "../libs/crypto"; |
|
|
|
import { base64ToBlob, SHARED_PHOTO_BASE64_KEY } from "../libs/util"; |
|
|
|
import { logger } from "../utils/logger"; |
|
|
|
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; |
|
|
|
import { Temp } from "@/db/tables/temp"; |
|
|
|
@Component({ components: { PhotoDialog, QuickNav } }) |
|
|
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; |
|
|
|
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify"; |
|
|
|
import { |
|
|
|
NOTIFY_SHARED_PHOTO_LOAD_ERROR, |
|
|
|
NOTIFY_SHARED_PHOTO_SAVE_ERROR, |
|
|
|
} from "@/constants/notifications"; |
|
|
|
@Component({ |
|
|
|
components: { PhotoDialog, QuickNav }, |
|
|
|
mixins: [PlatformServiceMixin], |
|
|
|
}) |
|
|
|
export default class SharedPhotoView extends Vue { |
|
|
|
$notify!: (notification: NotificationIface, timeout?: number) => void; |
|
|
|
$router!: Router; |
|
|
|
$route!: RouteLocationNormalizedLoaded; |
|
|
|
|
|
|
|
notify!: ReturnType<typeof createNotifyHelpers>; |
|
|
|
|
|
|
|
/** |
|
|
|
* Active user's DID for authentication and image ownership |
|
|
|
*/ |
|
|
|
activeDid: string | undefined = undefined; |
|
|
|
|
|
|
|
/** |
|
|
|
* Blob data of the shared image for processing and upload |
|
|
|
*/ |
|
|
|
imageBlob: Blob | undefined = undefined; |
|
|
|
|
|
|
|
/** |
|
|
|
* Original filename of the shared image from external app |
|
|
|
*/ |
|
|
|
imageFileName: string | undefined = undefined; |
|
|
|
|
|
|
|
/** |
|
|
|
* Upload status indicator for UI feedback |
|
|
|
*/ |
|
|
|
uploading = false; |
|
|
|
|
|
|
|
/** |
|
|
|
* Browser URL API for creating object URLs from blobs |
|
|
|
*/ |
|
|
|
URL = window.URL || window.webkitURL; |
|
|
|
|
|
|
|
// 'created' hook runs when the Vue instance is first created |
|
|
|
/** |
|
|
|
* Vue lifecycle hook - Initialize shared image processing |
|
|
|
* |
|
|
|
* Loads the shared image data from temporary storage, retrieves user settings, |
|
|
|
* and prepares the component for image processing. Cleans up temporary storage |
|
|
|
* after successful image loading. |
|
|
|
* |
|
|
|
* @async |
|
|
|
*/ |
|
|
|
async mounted() { |
|
|
|
this.notify = createNotifyHelpers(this.$notify); |
|
|
|
|
|
|
|
try { |
|
|
|
const settings = await databaseUtil.retrieveSettingsForActiveAccount(); |
|
|
|
const settings = await this.$accountSettings(); |
|
|
|
this.activeDid = settings.activeDid; |
|
|
|
|
|
|
|
const platformService = PlatformServiceFactory.getInstance(); |
|
|
|
const tempQuery = await platformService.dbQuery( |
|
|
|
"SELECT * FROM temp WHERE id = ?", |
|
|
|
[SHARED_PHOTO_BASE64_KEY], |
|
|
|
); |
|
|
|
const temp = databaseUtil.mapQueryResultToValues(tempQuery)?.[0] as Temp; |
|
|
|
const temp = await this.$first<Temp>("SELECT * FROM temp WHERE id = ?", [ |
|
|
|
SHARED_PHOTO_BASE64_KEY, |
|
|
|
]); |
|
|
|
const imageB64 = temp?.blobB64 as string; |
|
|
|
if (temp) { |
|
|
|
this.imageBlob = base64ToBlob(imageB64); |
|
|
|
|
|
|
|
// clear the temp image |
|
|
|
await platformService.dbExec("DELETE FROM temp WHERE id = ?", [ |
|
|
|
await this.$dbExec("DELETE FROM temp WHERE id = ?", [ |
|
|
|
SHARED_PHOTO_BASE64_KEY, |
|
|
|
]); |
|
|
|
|
|
|
@ -125,18 +196,21 @@ export default class SharedPhotoView extends Vue { |
|
|
|
} |
|
|
|
} catch (err: unknown) { |
|
|
|
logger.error("Got an error loading an identifier:", err); |
|
|
|
this.$notify( |
|
|
|
{ |
|
|
|
group: "alert", |
|
|
|
type: "danger", |
|
|
|
title: "Error", |
|
|
|
text: "Got an error loading this data.", |
|
|
|
}, |
|
|
|
3000, |
|
|
|
this.notify.error( |
|
|
|
NOTIFY_SHARED_PHOTO_LOAD_ERROR.message, |
|
|
|
TIMEOUTS.STANDARD, |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Process shared image as a gift record |
|
|
|
* |
|
|
|
* Uploads the shared image to the server as a GiveAction and navigates |
|
|
|
* to the gift details view with the uploaded image URL for gift recording. |
|
|
|
* |
|
|
|
* @async |
|
|
|
*/ |
|
|
|
async recordGift() { |
|
|
|
await this.sendToImageServer("GiveAction").then((url) => { |
|
|
|
if (url) { |
|
|
@ -155,10 +229,17 @@ export default class SharedPhotoView extends Vue { |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Process shared image as profile picture |
|
|
|
* |
|
|
|
* Opens the PhotoDialog component for image cropping and profile picture |
|
|
|
* processing. Updates user settings with the new profile image URL and |
|
|
|
* navigates to the account view upon completion. |
|
|
|
*/ |
|
|
|
recordProfile() { |
|
|
|
(this.$refs.photoDialog as PhotoDialog).open( |
|
|
|
async (imgUrl) => { |
|
|
|
databaseUtil.updateDefaultSettings({ profileImageUrl: imgUrl }); |
|
|
|
await this.$updateSettings({ profileImageUrl: imgUrl }); |
|
|
|
this.$router.push({ name: "account" }); |
|
|
|
}, |
|
|
|
IMAGE_TYPE_PROFILE, |
|
|
@ -168,12 +249,31 @@ export default class SharedPhotoView extends Vue { |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Cancel image processing and return to home |
|
|
|
* |
|
|
|
* Clears the shared image data and navigates back to the home view, |
|
|
|
* effectively canceling the image sharing operation. |
|
|
|
* |
|
|
|
* @async |
|
|
|
*/ |
|
|
|
async cancel() { |
|
|
|
this.imageBlob = undefined; |
|
|
|
this.imageFileName = undefined; |
|
|
|
this.$router.push({ name: "home" }); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Upload image to the image server with comprehensive error handling |
|
|
|
* |
|
|
|
* Sends the shared image to the configured image server with JWT authentication. |
|
|
|
* Provides detailed error handling for various failure scenarios including |
|
|
|
* authentication, file size, format, and server errors. |
|
|
|
* |
|
|
|
* @param imageType - The type of image claim (e.g., "GiveAction") |
|
|
|
* @returns Promise<string | undefined> - The uploaded image URL or undefined on failure |
|
|
|
* @async |
|
|
|
*/ |
|
|
|
async sendToImageServer(imageType: string) { |
|
|
|
this.uploading = true; |
|
|
|
|
|
|
@ -212,17 +312,11 @@ export default class SharedPhotoView extends Vue { |
|
|
|
result = response.data.url as string; |
|
|
|
} else { |
|
|
|
logger.error("Problem uploading the image", response.data); |
|
|
|
this.$notify( |
|
|
|
{ |
|
|
|
group: "alert", |
|
|
|
type: "danger", |
|
|
|
title: "Error", |
|
|
|
text: |
|
|
|
"There was a problem saving the picture. " + |
|
|
|
(response?.data?.message || ""), |
|
|
|
}, |
|
|
|
5000, |
|
|
|
); |
|
|
|
const errorMessage = |
|
|
|
NOTIFY_SHARED_PHOTO_SAVE_ERROR.message + |
|
|
|
" " + |
|
|
|
(response?.data?.message || ""); |
|
|
|
this.notify.error(errorMessage, TIMEOUTS.LONG); |
|
|
|
} |
|
|
|
|
|
|
|
this.uploading = false; |
|
|
@ -278,15 +372,7 @@ export default class SharedPhotoView extends Vue { |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
this.$notify( |
|
|
|
{ |
|
|
|
group: "alert", |
|
|
|
type: "danger", |
|
|
|
title: "Error", |
|
|
|
text: errorMessage, |
|
|
|
}, |
|
|
|
5000, |
|
|
|
); |
|
|
|
this.notify.error(errorMessage, TIMEOUTS.LONG); |
|
|
|
this.uploading = false; |
|
|
|
} |
|
|
|
return result; |
|
|
|