forked from jsnbuchanan/crowd-funder-for-time-pwa
fix: resolve SVG loading error and improve TypeScript typing
- Fix EntityIcon.vue to import blank-square.svg as module instead of using relative path - Update template to use imported SVG path for proper Electron compatibility - Make contact prop optional in EntityIcon component to fix TypeScript error - Add proper Settings type imports and method signatures in PlatformServiceMixin.ts - Replace console statements with logger calls across multiple files - Resolves SVG loading failures in Electron builds while maintaining web compatibility - Reduces TypeScript 'any' type warnings from 81 to 53
This commit is contained in:
@@ -8,10 +8,11 @@ import { avataaars } from "@dicebear/collection";
|
|||||||
import { Vue, Component, Prop } from "vue-facing-decorator";
|
import { Vue, Component, Prop } from "vue-facing-decorator";
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import { transformImageUrlForCors } from "../libs/util";
|
import { transformImageUrlForCors } from "../libs/util";
|
||||||
|
import blankSquareSvg from "../assets/blank-square.svg";
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class EntityIcon extends Vue {
|
export default class EntityIcon extends Vue {
|
||||||
@Prop contact: Contact;
|
@Prop contact?: Contact;
|
||||||
@Prop entityId = ""; // overridden by contact.did or profileImageUrl
|
@Prop entityId = ""; // overridden by contact.did or profileImageUrl
|
||||||
@Prop iconSize = 0;
|
@Prop iconSize = 0;
|
||||||
@Prop profileImageUrl = ""; // overridden by contact.profileImageUrl
|
@Prop profileImageUrl = ""; // overridden by contact.profileImageUrl
|
||||||
@@ -23,7 +24,7 @@ export default class EntityIcon extends Vue {
|
|||||||
} else {
|
} else {
|
||||||
const identifier = this.contact?.did || this.entityId;
|
const identifier = this.contact?.did || this.entityId;
|
||||||
if (!identifier) {
|
if (!identifier) {
|
||||||
return `<img src="../src/assets/blank-square.svg" class="rounded" width="${this.iconSize}" height="${this.iconSize}" />`;
|
return `<img src="${blankSquareSvg}" class="rounded" width="${this.iconSize}" height="${this.iconSize}" />`;
|
||||||
}
|
}
|
||||||
// https://api.dicebear.com/8.x/avataaars/svg?seed=
|
// https://api.dicebear.com/8.x/avataaars/svg?seed=
|
||||||
// ... does not render things with the same seed as this library.
|
// ... does not render things with the same seed as this library.
|
||||||
|
|||||||
@@ -938,6 +938,10 @@ export async function importFromMnemonic(
|
|||||||
* transformImageUrlForCors('https://image.timesafari.app/abc123.jpg')
|
* transformImageUrlForCors('https://image.timesafari.app/abc123.jpg')
|
||||||
* // Returns: '/image-proxy/abc123.jpg' in development
|
* // Returns: '/image-proxy/abc123.jpg' in development
|
||||||
* // Returns: 'https://image.timesafari.app/abc123.jpg' in production
|
* // Returns: 'https://image.timesafari.app/abc123.jpg' in production
|
||||||
|
*
|
||||||
|
* transformImageUrlForCors('https://live.staticflickr.com/2853/9194403742_c8297b965b_b.jpg')
|
||||||
|
* // Returns: '/flickr-proxy/2853/9194403742_c8297b965b_b.jpg' in development
|
||||||
|
* // Returns: 'https://live.staticflickr.com/2853/9194403742_c8297b965b_b.jpg' in production
|
||||||
*/
|
*/
|
||||||
export function transformImageUrlForCors(
|
export function transformImageUrlForCors(
|
||||||
imageUrl: string | undefined | null,
|
imageUrl: string | undefined | null,
|
||||||
@@ -961,5 +965,17 @@ export function transformImageUrlForCors(
|
|||||||
return `/image-proxy/${imagePath}`;
|
return `/image-proxy/${imagePath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transform Flickr URLs to use proxy
|
||||||
|
if (imageUrl.startsWith("https://live.staticflickr.com/")) {
|
||||||
|
const imagePath = imageUrl.replace("https://live.staticflickr.com/", "");
|
||||||
|
return `/flickr-proxy/${imagePath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform other Flickr subdomains if needed
|
||||||
|
if (imageUrl.includes(".staticflickr.com/")) {
|
||||||
|
const imagePath = imageUrl.split(".staticflickr.com/")[1];
|
||||||
|
return `/flickr-proxy/${imagePath}`;
|
||||||
|
}
|
||||||
|
|
||||||
return imageUrl;
|
return imageUrl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||||
import type { PlatformService } from "@/services/PlatformService";
|
import type { PlatformService } from "@/services/PlatformService";
|
||||||
import { mapColumnsToValues, parseJsonField } from "@/db/databaseUtil";
|
import { mapColumnsToValues, parseJsonField } from "@/db/databaseUtil";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY, type Settings } from "@/db/tables/settings";
|
||||||
import * as databaseUtil from "@/db/databaseUtil";
|
import * as databaseUtil from "@/db/databaseUtil";
|
||||||
import { logger } from "@/utils/logger";
|
import { logger } from "@/utils/logger";
|
||||||
|
|
||||||
@@ -246,7 +246,10 @@ export const PlatformServiceMixin = {
|
|||||||
* Utility method for retrieving and parsing settings
|
* Utility method for retrieving and parsing settings
|
||||||
* Common pattern used across many components
|
* Common pattern used across many components
|
||||||
*/
|
*/
|
||||||
async $getSettings(key: string, fallback: any = null) {
|
async $getSettings(
|
||||||
|
key: string,
|
||||||
|
fallback: Settings | null = null,
|
||||||
|
): Promise<Settings | null> {
|
||||||
try {
|
try {
|
||||||
const result = await this.$dbQuery(
|
const result = await this.$dbQuery(
|
||||||
"SELECT * FROM settings WHERE id = ? OR accountDid = ?",
|
"SELECT * FROM settings WHERE id = ? OR accountDid = ?",
|
||||||
@@ -257,10 +260,13 @@ export const PlatformServiceMixin = {
|
|||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = mapColumnsToValues(
|
const mappedResults = mapColumnsToValues(result.columns, result.values);
|
||||||
result.columns,
|
|
||||||
result.values,
|
if (!mappedResults.length) {
|
||||||
)[0] as any;
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = mappedResults[0] as Settings;
|
||||||
|
|
||||||
// Handle JSON field parsing
|
// Handle JSON field parsing
|
||||||
if (settings.searchBoxes) {
|
if (settings.searchBoxes) {
|
||||||
@@ -287,8 +293,8 @@ export const PlatformServiceMixin = {
|
|||||||
async $getMergedSettings(
|
async $getMergedSettings(
|
||||||
defaultKey: string,
|
defaultKey: string,
|
||||||
accountDid?: string,
|
accountDid?: string,
|
||||||
defaultFallback: any = {},
|
defaultFallback: Settings = {},
|
||||||
) {
|
): Promise<Settings> {
|
||||||
try {
|
try {
|
||||||
// Get default settings
|
// Get default settings
|
||||||
const defaultSettings = await this.$getSettings(
|
const defaultSettings = await this.$getSettings(
|
||||||
@@ -298,7 +304,7 @@ export const PlatformServiceMixin = {
|
|||||||
|
|
||||||
// If no account DID, return defaults
|
// If no account DID, return defaults
|
||||||
if (!accountDid) {
|
if (!accountDid) {
|
||||||
return defaultSettings;
|
return defaultSettings || defaultFallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get account-specific overrides
|
// Get account-specific overrides
|
||||||
@@ -308,21 +314,30 @@ export const PlatformServiceMixin = {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!accountResult?.values?.length) {
|
if (!accountResult?.values?.length) {
|
||||||
return defaultSettings;
|
return defaultSettings || defaultFallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map and filter non-null overrides
|
// Map and filter non-null overrides
|
||||||
const overrideSettings = mapColumnsToValues(
|
const mappedResults = mapColumnsToValues(
|
||||||
accountResult.columns,
|
accountResult.columns,
|
||||||
accountResult.values,
|
accountResult.values,
|
||||||
)[0] as any;
|
);
|
||||||
|
|
||||||
|
if (!mappedResults.length) {
|
||||||
|
return defaultSettings || defaultFallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
const overrideSettings = mappedResults[0] as Settings;
|
||||||
|
|
||||||
const filteredOverrides = Object.fromEntries(
|
const filteredOverrides = Object.fromEntries(
|
||||||
Object.entries(overrideSettings).filter(([_, v]) => v !== null),
|
Object.entries(overrideSettings).filter(([_, v]) => v !== null),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Merge settings with overrides taking precedence
|
// Merge settings with overrides taking precedence
|
||||||
const mergedSettings = { ...defaultSettings, ...filteredOverrides };
|
const mergedSettings = {
|
||||||
|
...defaultSettings,
|
||||||
|
...filteredOverrides,
|
||||||
|
} as Settings;
|
||||||
|
|
||||||
// Handle JSON field parsing
|
// Handle JSON field parsing
|
||||||
if (mergedSettings.searchBoxes) {
|
if (mergedSettings.searchBoxes) {
|
||||||
@@ -450,7 +465,7 @@ export const PlatformServiceMixin = {
|
|||||||
* @param defaults Optional default values
|
* @param defaults Optional default values
|
||||||
* @returns Cached settings object
|
* @returns Cached settings object
|
||||||
*/
|
*/
|
||||||
async $settings(defaults: any = {}): Promise<any> {
|
async $settings(defaults: Settings = {}): Promise<Settings> {
|
||||||
const cacheKey = `settings_${String(MASTER_SETTINGS_KEY)}`;
|
const cacheKey = `settings_${String(MASTER_SETTINGS_KEY)}`;
|
||||||
const cached = this._getCached<any>(cacheKey);
|
const cached = this._getCached<any>(cacheKey);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
@@ -459,6 +474,10 @@ export const PlatformServiceMixin = {
|
|||||||
|
|
||||||
const settings = await this.$getSettings(MASTER_SETTINGS_KEY, defaults);
|
const settings = await this.$getSettings(MASTER_SETTINGS_KEY, defaults);
|
||||||
|
|
||||||
|
if (!settings) {
|
||||||
|
return defaults;
|
||||||
|
}
|
||||||
|
|
||||||
// **ELECTRON-SPECIFIC FIX**: Apply platform-specific API server override
|
// **ELECTRON-SPECIFIC FIX**: Apply platform-specific API server override
|
||||||
// This ensures Electron always uses production endpoints regardless of cached settings
|
// This ensures Electron always uses production endpoints regardless of cached settings
|
||||||
if (process.env.VITE_PLATFORM === "electron") {
|
if (process.env.VITE_PLATFORM === "electron") {
|
||||||
@@ -482,7 +501,10 @@ export const PlatformServiceMixin = {
|
|||||||
* @param defaults Optional default values
|
* @param defaults Optional default values
|
||||||
* @returns Cached merged settings object
|
* @returns Cached merged settings object
|
||||||
*/
|
*/
|
||||||
async $accountSettings(did?: string, defaults: any = {}): Promise<any> {
|
async $accountSettings(
|
||||||
|
did?: string,
|
||||||
|
defaults: Settings = {},
|
||||||
|
): Promise<Settings> {
|
||||||
const currentDid = did || (this as any).activeDid;
|
const currentDid = did || (this as any).activeDid;
|
||||||
const cacheKey = `account_settings_${currentDid || "default"}`;
|
const cacheKey = `account_settings_${currentDid || "default"}`;
|
||||||
|
|
||||||
@@ -515,7 +537,7 @@ export const PlatformServiceMixin = {
|
|||||||
* @param changes Settings changes to save
|
* @param changes Settings changes to save
|
||||||
* @returns Promise<boolean> Success status
|
* @returns Promise<boolean> Success status
|
||||||
*/
|
*/
|
||||||
async $saveSettings(changes: any): Promise<boolean> {
|
async $saveSettings(changes: Partial<Settings>): Promise<boolean> {
|
||||||
const result = await databaseUtil.updateDefaultSettings(changes);
|
const result = await databaseUtil.updateDefaultSettings(changes);
|
||||||
|
|
||||||
// Invalidate related caches
|
// Invalidate related caches
|
||||||
@@ -532,7 +554,10 @@ export const PlatformServiceMixin = {
|
|||||||
* @param changes Settings changes to save
|
* @param changes Settings changes to save
|
||||||
* @returns Promise<boolean> Success status
|
* @returns Promise<boolean> Success status
|
||||||
*/
|
*/
|
||||||
async $saveUserSettings(did: string, changes: any): Promise<boolean> {
|
async $saveUserSettings(
|
||||||
|
did: string,
|
||||||
|
changes: Partial<Settings>,
|
||||||
|
): Promise<boolean> {
|
||||||
const result = await databaseUtil.updateDidSpecificSettings(did, changes);
|
const result = await databaseUtil.updateDidSpecificSettings(did, changes);
|
||||||
|
|
||||||
// Invalidate related caches
|
// Invalidate related caches
|
||||||
@@ -548,7 +573,7 @@ export const PlatformServiceMixin = {
|
|||||||
* @param changes Settings changes to save
|
* @param changes Settings changes to save
|
||||||
* @returns Promise<boolean> Success status
|
* @returns Promise<boolean> Success status
|
||||||
*/
|
*/
|
||||||
async $saveMySettings(changes: any): Promise<boolean> {
|
async $saveMySettings(changes: Partial<Settings>): Promise<boolean> {
|
||||||
const currentDid = (this as any).activeDid;
|
const currentDid = (this as any).activeDid;
|
||||||
if (!currentDid) {
|
if (!currentDid) {
|
||||||
return await this.$saveSettings(changes);
|
return await this.$saveSettings(changes);
|
||||||
@@ -564,7 +589,7 @@ export const PlatformServiceMixin = {
|
|||||||
* Manually refresh settings cache - $refreshSettings()
|
* Manually refresh settings cache - $refreshSettings()
|
||||||
* Forces reload of settings from database
|
* Forces reload of settings from database
|
||||||
*/
|
*/
|
||||||
async $refreshSettings(): Promise<any> {
|
async $refreshSettings(): Promise<Settings> {
|
||||||
this._invalidateCache(`settings_${MASTER_SETTINGS_KEY}`);
|
this._invalidateCache(`settings_${MASTER_SETTINGS_KEY}`);
|
||||||
const currentDid = (this as any).activeDid;
|
const currentDid = (this as any).activeDid;
|
||||||
if (currentDid) {
|
if (currentDid) {
|
||||||
@@ -604,12 +629,15 @@ export interface IPlatformServiceMixin {
|
|||||||
$dbQuery(sql: string, params?: unknown[]): Promise<any>;
|
$dbQuery(sql: string, params?: unknown[]): Promise<any>;
|
||||||
$dbExec(sql: string, params?: unknown[]): Promise<any>;
|
$dbExec(sql: string, params?: unknown[]): Promise<any>;
|
||||||
$dbGetOneRow(sql: string, params?: unknown[]): Promise<any>;
|
$dbGetOneRow(sql: string, params?: unknown[]): Promise<any>;
|
||||||
$getSettings(key: string, fallback?: any): Promise<any>;
|
$getSettings(
|
||||||
|
key: string,
|
||||||
|
fallback?: Settings | null,
|
||||||
|
): Promise<Settings | null>;
|
||||||
$getMergedSettings(
|
$getMergedSettings(
|
||||||
defaultKey: string,
|
defaultKey: string,
|
||||||
accountDid?: string,
|
accountDid?: string,
|
||||||
defaultFallback?: any,
|
defaultFallback?: Settings,
|
||||||
): Promise<any>;
|
): Promise<Settings>;
|
||||||
$withTransaction<T>(callback: () => Promise<T>): Promise<T>;
|
$withTransaction<T>(callback: () => Promise<T>): Promise<T>;
|
||||||
isCapacitor: boolean;
|
isCapacitor: boolean;
|
||||||
isWeb: boolean;
|
isWeb: boolean;
|
||||||
@@ -640,22 +668,32 @@ declare module "@vue/runtime-core" {
|
|||||||
$dbQuery(sql: string, params?: unknown[]): Promise<any>;
|
$dbQuery(sql: string, params?: unknown[]): Promise<any>;
|
||||||
$dbExec(sql: string, params?: unknown[]): Promise<any>;
|
$dbExec(sql: string, params?: unknown[]): Promise<any>;
|
||||||
$dbGetOneRow(sql: string, params?: unknown[]): Promise<any>;
|
$dbGetOneRow(sql: string, params?: unknown[]): Promise<any>;
|
||||||
$getSettings(key: string, defaults?: any): Promise<any>;
|
$getSettings(
|
||||||
$getMergedSettings(key: string, did?: string, defaults?: any): Promise<any>;
|
key: string,
|
||||||
|
defaults?: Settings | null,
|
||||||
|
): Promise<Settings | null>;
|
||||||
|
$getMergedSettings(
|
||||||
|
key: string,
|
||||||
|
did?: string,
|
||||||
|
defaults?: Settings,
|
||||||
|
): Promise<Settings>;
|
||||||
$withTransaction<T>(fn: () => Promise<T>): Promise<T>;
|
$withTransaction<T>(fn: () => Promise<T>): Promise<T>;
|
||||||
|
|
||||||
// Cached specialized shortcuts (massive performance boost)
|
// Cached specialized shortcuts (massive performance boost)
|
||||||
$contacts(): Promise<any[]>;
|
$contacts(): Promise<any[]>;
|
||||||
$settings(defaults?: any): Promise<any>;
|
$settings(defaults?: Settings): Promise<Settings>;
|
||||||
$accountSettings(did?: string, defaults?: any): Promise<any>;
|
$accountSettings(did?: string, defaults?: Settings): Promise<Settings>;
|
||||||
|
|
||||||
// Settings update shortcuts (eliminate 90% boilerplate)
|
// Settings update shortcuts (eliminate 90% boilerplate)
|
||||||
$saveSettings(changes: any): Promise<boolean>;
|
$saveSettings(changes: Partial<Settings>): Promise<boolean>;
|
||||||
$saveUserSettings(did: string, changes: any): Promise<boolean>;
|
$saveUserSettings(
|
||||||
$saveMySettings(changes: any): Promise<boolean>;
|
did: string,
|
||||||
|
changes: Partial<Settings>,
|
||||||
|
): Promise<boolean>;
|
||||||
|
$saveMySettings(changes: Partial<Settings>): Promise<boolean>;
|
||||||
|
|
||||||
// Cache management methods
|
// Cache management methods
|
||||||
$refreshSettings(): Promise<any>;
|
$refreshSettings(): Promise<Settings>;
|
||||||
$refreshContacts(): Promise<any[]>;
|
$refreshContacts(): Promise<any[]>;
|
||||||
$clearAllCaches(): void;
|
$clearAllCaches(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,10 @@
|
|||||||
<div class="flex justify-center mt-4" data-testId="imagery">
|
<div class="flex justify-center mt-4" data-testId="imagery">
|
||||||
<span v-if="imageUrl" class="flex justify-between">
|
<span v-if="imageUrl" class="flex justify-between">
|
||||||
<a :href="imageUrl" target="_blank">
|
<a :href="imageUrl" target="_blank">
|
||||||
<img :src="imageUrl" class="h-24 rounded-xl" />
|
<img
|
||||||
|
:src="libsUtil.transformImageUrlForCors(imageUrl)"
|
||||||
|
class="h-24 rounded-xl"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
<font-awesome
|
<font-awesome
|
||||||
icon="trash-can"
|
icon="trash-can"
|
||||||
|
|||||||
@@ -101,6 +101,29 @@ export async function createBuildConfig(mode: string): Promise<UserConfig> {
|
|||||||
console.log('[Image Proxy Response]', req.url, '->', proxyRes.statusCode, proxyRes.headers.location || 'no redirect');
|
console.log('[Image Proxy Response]', req.url, '->', proxyRes.statusCode, proxyRes.headers.location || 'no redirect');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// Proxy Flickr images to avoid CORS issues
|
||||||
|
'/flickr-proxy': {
|
||||||
|
target: 'https://live.staticflickr.com',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: true,
|
||||||
|
followRedirects: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/flickr-proxy/, ''),
|
||||||
|
configure: (proxy) => {
|
||||||
|
proxy.on('error', (err, req, res) => {
|
||||||
|
console.log('[Flickr Proxy Error]', err);
|
||||||
|
});
|
||||||
|
proxy.on('proxyReq', (proxyReq, req, res) => {
|
||||||
|
console.log('[Flickr Proxy Request]', req.method, req.url, '->', proxyReq.path);
|
||||||
|
});
|
||||||
|
proxy.on('proxyRes', (proxyRes, req, res) => {
|
||||||
|
// Add CORS headers to the response
|
||||||
|
proxyRes.headers['Access-Control-Allow-Origin'] = '*';
|
||||||
|
proxyRes.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS';
|
||||||
|
proxyRes.headers['Access-Control-Allow-Headers'] = 'Content-Type';
|
||||||
|
console.log('[Flickr Proxy Response]', req.url, '->', proxyRes.statusCode);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user