Compare commits
2 Commits
homeview-c
...
electron_f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae25a066f2 | ||
|
|
4230deab1d |
36
electron-builder.json
Normal file
36
electron-builder.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"appId": "app.timesafari.app",
|
||||||
|
"productName": "TimeSafari",
|
||||||
|
"directories": {
|
||||||
|
"output": "dist-electron-packages",
|
||||||
|
"buildResources": "build"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist-electron/**/*",
|
||||||
|
"node_modules/**/*",
|
||||||
|
"package.json",
|
||||||
|
"src/electron/electron-logger.js"
|
||||||
|
],
|
||||||
|
"extraResources": [
|
||||||
|
{
|
||||||
|
"from": "src/utils",
|
||||||
|
"to": "utils",
|
||||||
|
"filter": ["**/*"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extraMetadata": {
|
||||||
|
"main": "src/electron/main.js"
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"target": ["AppImage"],
|
||||||
|
"category": "Utility",
|
||||||
|
"maintainer": "TimeSafari Team"
|
||||||
|
},
|
||||||
|
"mac": {
|
||||||
|
"target": ["dmg"],
|
||||||
|
"category": "public.app-category.productivity"
|
||||||
|
},
|
||||||
|
"win": {
|
||||||
|
"target": ["nsis"]
|
||||||
|
}
|
||||||
|
}
|
||||||
25543
package-lock.json
generated
25543
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
52
package.json
52
package.json
@@ -22,14 +22,14 @@
|
|||||||
"check:ios-device": "xcrun xctrace list devices 2>&1 | grep -w 'Booted' || (echo 'No iOS simulator running' && exit 1)",
|
"check:ios-device": "xcrun xctrace list devices 2>&1 | grep -w 'Booted' || (echo 'No iOS simulator running' && exit 1)",
|
||||||
"clean:electron": "rimraf dist-electron",
|
"clean:electron": "rimraf dist-electron",
|
||||||
"build:pywebview": "vite build --config vite.config.pywebview.mts",
|
"build:pywebview": "vite build --config vite.config.pywebview.mts",
|
||||||
"build:electron": "npm run clean:electron && vite build --config vite.config.electron.mts && node scripts/build-electron.js",
|
"build:electron": "npm run check:electron && npm run clean:electron && vite build --config vite.config.electron.mts && node scripts/build-electron.js",
|
||||||
"build:capacitor": "vite build --config vite.config.capacitor.mts",
|
"build:capacitor": "vite build --config vite.config.capacitor.mts",
|
||||||
"build:web": "vite build --config vite.config.web.mts",
|
"build:web": "vite build --config vite.config.web.mts",
|
||||||
"electron:dev": "npm run build && electron dist-electron",
|
"electron:dev": "concurrently \"vite --config vite.config.electron.mts\" \"electron .\"",
|
||||||
"electron:start": "electron dist-electron",
|
"electron:start": "electron dist-electron",
|
||||||
"electron:build-linux": "npm run build:electron && electron-builder --linux AppImage",
|
"electron:build-linux": "npm run check:electron && npm run build:electron && electron-builder --linux AppImage",
|
||||||
"electron:build-linux-deb": "npm run build:electron && electron-builder --linux deb",
|
"electron:build-linux-deb": "npm run check:electron && npm run build:electron && electron-builder --linux deb",
|
||||||
"electron:build-linux-prod": "NODE_ENV=production npm run build:electron && electron-builder --linux AppImage",
|
"electron:build-linux-prod": "NODE_ENV=production npm run check:electron &&npm run build:electron && electron-builder --linux AppImage",
|
||||||
"build:electron-prod": "NODE_ENV=production npm run build:electron",
|
"build:electron-prod": "NODE_ENV=production npm run build:electron",
|
||||||
"pywebview:dev": "vite build --config vite.config.pywebview.mts && .venv/bin/python src/pywebview/main.py",
|
"pywebview:dev": "vite build --config vite.config.pywebview.mts && .venv/bin/python src/pywebview/main.py",
|
||||||
"pywebview:build": "vite build --config vite.config.pywebview.mts && .venv/bin/python src/pywebview/main.py",
|
"pywebview:build": "vite build --config vite.config.pywebview.mts && .venv/bin/python src/pywebview/main.py",
|
||||||
@@ -39,7 +39,10 @@
|
|||||||
"fastlane:ios:beta": "cd ios && fastlane beta",
|
"fastlane:ios:beta": "cd ios && fastlane beta",
|
||||||
"fastlane:ios:release": "cd ios && fastlane release",
|
"fastlane:ios:release": "cd ios && fastlane release",
|
||||||
"fastlane:android:beta": "cd android && fastlane beta",
|
"fastlane:android:beta": "cd android && fastlane beta",
|
||||||
"fastlane:android:release": "cd android && fastlane release"
|
"fastlane:android:release": "cd android && fastlane release",
|
||||||
|
"check:electron": "node scripts/check-electron-prerequisites.js",
|
||||||
|
"electron:build": "npm run check:electron && vite build --config vite.config.electron.mts && node scripts/fix-electron-paths.js && electron-builder",
|
||||||
|
"postinstall": "electron-builder install-app-deps"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^6.2.0",
|
"@capacitor/android": "^6.2.0",
|
||||||
@@ -157,28 +160,31 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"appId": "app.timesafari",
|
"appId": "app.timesafari",
|
||||||
"productName": "TimeSafari",
|
"productName": "TimeSafari",
|
||||||
"directories": {
|
|
||||||
"output": "dist-electron-packages"
|
|
||||||
},
|
|
||||||
"files": [
|
"files": [
|
||||||
"dist-electron/**/*",
|
"dist-electron/**/*",
|
||||||
"src/electron/**/*",
|
"!dist-electron/node_modules/**/*"
|
||||||
"main.js"
|
|
||||||
],
|
],
|
||||||
"extraResources": [
|
"directories": {
|
||||||
{
|
"output": "dist-electron-packages",
|
||||||
"from": "dist-electron",
|
"buildResources": "build-resources"
|
||||||
"to": "."
|
},
|
||||||
}
|
"extraResources": [],
|
||||||
|
"asar": true,
|
||||||
|
"asarUnpack": [
|
||||||
|
"dist-electron/www/assets/**/*"
|
||||||
],
|
],
|
||||||
"linux": {
|
"linux": {
|
||||||
"target": [
|
"target": ["AppImage"],
|
||||||
"AppImage",
|
"category": "Utility",
|
||||||
"deb"
|
"executableName": "TimeSafari"
|
||||||
],
|
|
||||||
"category": "Office",
|
|
||||||
"icon": "build/icon.png"
|
|
||||||
},
|
},
|
||||||
"asar": true
|
"mac": {
|
||||||
|
"category": "public.app-category.productivity"
|
||||||
|
},
|
||||||
|
"win": {
|
||||||
|
"target": ["nsis"]
|
||||||
|
},
|
||||||
|
"artifactName": "TimeSafari-${version}-${arch}.${ext}",
|
||||||
|
"publish": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,12 @@ async function main() {
|
|||||||
throw new Error('package.json not found in build directory');
|
throw new Error('package.json not found in build directory');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy the electron-logger.js file
|
||||||
|
const loggerSrc = path.join(__dirname, '../src/electron/electron-logger.js');
|
||||||
|
const loggerDest = path.join(distElectronDir, 'electron-logger.js');
|
||||||
|
fs.copyFileSync(loggerSrc, loggerDest);
|
||||||
|
console.log(`Copying src/electron/electron-logger.js to ${loggerDest}`);
|
||||||
|
|
||||||
console.log('Build completed successfully!');
|
console.log('Build completed successfully!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Build failed:', error);
|
console.error('Build failed:', error);
|
||||||
|
|||||||
177
scripts/check-electron-prerequisites.js
Normal file
177
scripts/check-electron-prerequisites.js
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file check-electron-prerequisites.js
|
||||||
|
* @description Verifies and installs required dependencies for Electron builds
|
||||||
|
*
|
||||||
|
* This script checks if Python's distutils module is available, which is required
|
||||||
|
* by node-gyp when compiling native Node.js modules during Electron packaging.
|
||||||
|
* Without distutils, builds will fail with "ModuleNotFoundError: No module named 'distutils'".
|
||||||
|
*
|
||||||
|
* The script performs the following actions:
|
||||||
|
* 1. Checks if Python's distutils module is available
|
||||||
|
* 2. If missing, offers to install setuptools package which provides distutils
|
||||||
|
* 3. Attempts installation through pip or pip3
|
||||||
|
* 4. Provides manual installation instructions if automated installation fails
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* - Direct execution: node scripts/check-electron-prerequisites.js
|
||||||
|
* - As npm script: npm run check:electron
|
||||||
|
* - Before builds: npm run check:electron && electron-builder
|
||||||
|
*
|
||||||
|
* Exit codes:
|
||||||
|
* - 0: All prerequisites are met or were successfully installed
|
||||||
|
* - 1: Prerequisites are missing and weren't installed
|
||||||
|
*
|
||||||
|
* @author [YOUR_NAME]
|
||||||
|
* @version 1.0.0
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
const readline = require('readline');
|
||||||
|
const chalk = require('chalk'); // You might need to add this to your dependencies
|
||||||
|
|
||||||
|
console.log(chalk.blue('🔍 Checking Electron build prerequisites...'));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if Python's distutils module is available
|
||||||
|
*
|
||||||
|
* This function attempts to import the distutils module in Python.
|
||||||
|
* If successful, it means node-gyp will be able to compile native modules.
|
||||||
|
* If unsuccessful, the Electron build will likely fail when compiling native dependencies.
|
||||||
|
*
|
||||||
|
* @returns {boolean} True if distutils is available, false otherwise
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* if (checkDistutils()) {
|
||||||
|
* console.log('Ready to build Electron app');
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
function checkDistutils() {
|
||||||
|
try {
|
||||||
|
// Attempt to import distutils using Python
|
||||||
|
// We use stdio: 'ignore' to suppress any Python output
|
||||||
|
execSync('python -c "import distutils"', { stdio: 'ignore' });
|
||||||
|
console.log(chalk.green('✅ Python distutils is available'));
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
// This error occurs if either Python is not found or if distutils is missing
|
||||||
|
console.log(chalk.red('❌ Python distutils is missing'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the setuptools package which provides distutils
|
||||||
|
*
|
||||||
|
* This function attempts to install setuptools using pip or pip3.
|
||||||
|
* Setuptools is a package that provides the distutils module needed by node-gyp.
|
||||||
|
* In Python 3.12+, distutils was moved out of the standard library into setuptools.
|
||||||
|
*
|
||||||
|
* The function tries multiple installation methods:
|
||||||
|
* 1. First attempts with pip
|
||||||
|
* 2. If that fails, tries with pip3
|
||||||
|
* 3. If both fail, provides instructions for manual installation
|
||||||
|
*
|
||||||
|
* @returns {Promise<boolean>} True if installation succeeded, false otherwise
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const success = await installSetuptools();
|
||||||
|
* if (success) {
|
||||||
|
* console.log('Ready to proceed with build');
|
||||||
|
* } else {
|
||||||
|
* console.log('Please fix prerequisites manually');
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
async function installSetuptools() {
|
||||||
|
console.log(chalk.yellow('📦 Attempting to install setuptools...'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
// First try with pip, commonly used on all platforms
|
||||||
|
execSync('pip install setuptools', { stdio: 'inherit' });
|
||||||
|
console.log(chalk.green('✅ Successfully installed setuptools'));
|
||||||
|
return true;
|
||||||
|
} catch (pipError) {
|
||||||
|
try {
|
||||||
|
// If pip fails, try with pip3 (common on Linux distributions)
|
||||||
|
console.log(chalk.yellow('⚠️ Trying with pip3...'));
|
||||||
|
execSync('pip3 install setuptools', { stdio: 'inherit' });
|
||||||
|
console.log(chalk.green('✅ Successfully installed setuptools using pip3'));
|
||||||
|
return true;
|
||||||
|
} catch (pip3Error) {
|
||||||
|
// If both methods fail, provide manual installation guidance
|
||||||
|
console.log(chalk.red('❌ Failed to install setuptools automatically'));
|
||||||
|
console.log(chalk.yellow('📝 Please install it manually with:'));
|
||||||
|
console.log(' pip install setuptools');
|
||||||
|
console.log(' or');
|
||||||
|
console.log(' sudo apt install python3-setuptools (on Debian/Ubuntu)');
|
||||||
|
console.log(' sudo pacman -S python-setuptools (on Arch Linux)');
|
||||||
|
console.log(' sudo dnf install python3-setuptools (on Fedora)');
|
||||||
|
console.log(' brew install python-setuptools (on macOS with Homebrew)');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main execution function
|
||||||
|
*
|
||||||
|
* This function orchestrates the checking and installation process:
|
||||||
|
* 1. Checks if distutils is already available
|
||||||
|
* 2. If not, informs the user and prompts for installation
|
||||||
|
* 3. Based on user input, attempts to install or exits
|
||||||
|
*
|
||||||
|
* The function handles interactive user prompts and orchestrates
|
||||||
|
* the overall flow of the script.
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @throws Will exit process with code 1 if prerequisites aren't met
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
// First check if distutils is already available
|
||||||
|
if (checkDistutils()) {
|
||||||
|
// All prerequisites are met, exit successfully
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inform the user about the missing prerequisite
|
||||||
|
console.log(chalk.yellow('⚠️ Python distutils is required for Electron builds'));
|
||||||
|
console.log(chalk.yellow('⚠️ This is needed to compile native modules during the build process'));
|
||||||
|
|
||||||
|
// Set up readline interface for user interaction
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prompt the user for installation permission
|
||||||
|
const answer = await new Promise(resolve => {
|
||||||
|
rl.question(chalk.blue('Would you like to install setuptools now? (y/n) '), resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up readline interface
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
if (answer.toLowerCase() === 'y') {
|
||||||
|
// User agreed to installation
|
||||||
|
const success = await installSetuptools();
|
||||||
|
if (success) {
|
||||||
|
// Installation succeeded, exit successfully
|
||||||
|
process.exit(0);
|
||||||
|
} else {
|
||||||
|
// Installation failed, exit with error
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// User declined installation
|
||||||
|
console.log(chalk.yellow('⚠️ Build may fail without distutils'));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the main function and handle any uncaught errors
|
||||||
|
main().catch(error => {
|
||||||
|
console.error(chalk.red('Error during prerequisites check:'), error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
61
scripts/fix-electron-paths.js
Normal file
61
scripts/fix-electron-paths.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Fix path resolution issues in the Electron build
|
||||||
|
*/
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const glob = require('glob');
|
||||||
|
|
||||||
|
// Fix asset paths in HTML file
|
||||||
|
function fixHtmlPaths() {
|
||||||
|
const htmlFile = path.join(__dirname, '../dist-electron/index.html');
|
||||||
|
if (fs.existsSync(htmlFile)) {
|
||||||
|
let html = fs.readFileSync(htmlFile, 'utf8');
|
||||||
|
|
||||||
|
// Convert absolute paths to relative
|
||||||
|
html = html.replace(/src="\//g, 'src="./');
|
||||||
|
html = html.replace(/href="\//g, 'href="./');
|
||||||
|
|
||||||
|
fs.writeFileSync(htmlFile, html);
|
||||||
|
console.log('✅ Fixed paths in index.html');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix asset imports in JS files
|
||||||
|
function fixJsPaths() {
|
||||||
|
const jsFiles = glob.sync('dist-electron/assets/*.js');
|
||||||
|
|
||||||
|
jsFiles.forEach(file => {
|
||||||
|
let content = fs.readFileSync(file, 'utf8');
|
||||||
|
|
||||||
|
// Replace absolute imports with relative ones
|
||||||
|
const originalContent = content;
|
||||||
|
content = content.replace(/["']\/assets\//g, '"./assets/');
|
||||||
|
|
||||||
|
if (content !== originalContent) {
|
||||||
|
fs.writeFileSync(file, content);
|
||||||
|
console.log(`✅ Fixed paths in ${path.basename(file)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add base href to HTML
|
||||||
|
function addBaseHref() {
|
||||||
|
const htmlFile = path.join(__dirname, '../dist-electron/index.html');
|
||||||
|
if (fs.existsSync(htmlFile)) {
|
||||||
|
let html = fs.readFileSync(htmlFile, 'utf8');
|
||||||
|
|
||||||
|
// Add base href if not present
|
||||||
|
if (!html.includes('<base href=')) {
|
||||||
|
html = html.replace('</head>', '<base href="./">\n</head>');
|
||||||
|
fs.writeFileSync(htmlFile, html);
|
||||||
|
console.log('✅ Added base href to index.html');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all fixes
|
||||||
|
fixHtmlPaths();
|
||||||
|
fixJsPaths();
|
||||||
|
addBaseHref();
|
||||||
|
|
||||||
|
console.log('🎉 Electron path fixes completed');
|
||||||
14
scripts/notarize.js
Normal file
14
scripts/notarize.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// This is a placeholder notarize script that does nothing for non-macOS platforms
|
||||||
|
// Only necessary for macOS app store submissions
|
||||||
|
|
||||||
|
exports.default = async function notarizing(context) {
|
||||||
|
// Only notarize macOS builds
|
||||||
|
if (context.electronPlatformName !== 'darwin') {
|
||||||
|
console.log('Skipping notarization for non-macOS platform');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For macOS, we would implement actual notarization here
|
||||||
|
console.log('This is where macOS notarization would happen');
|
||||||
|
// We're just returning with no action for non-macOS builds
|
||||||
|
};
|
||||||
@@ -10,26 +10,18 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="bg-slate-100 rounded-t-md border border-slate-300 p-3 sm:p-4">
|
||||||
class="flex items-center justify-between gap-2 text-lg bg-slate-200 border border-slate-300 border-b-0 rounded-t-md px-3 sm:px-4 py-1 sm:py-2"
|
<div class="flex items-center gap-2 mb-6">
|
||||||
>
|
<img
|
||||||
<div class="flex items-center gap-2">
|
src="https://a.fsdn.com/con/app/proj/identicons/screenshots/225506.jpg"
|
||||||
<div v-if="record.issuerDid">
|
class="size-8 object-cover rounded-full"
|
||||||
<EntityIcon
|
/>
|
||||||
:entity-id="record.issuerDid"
|
|
||||||
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<font-awesome
|
|
||||||
icon="person-circle-question"
|
|
||||||
class="text-slate-300 text-[2rem]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-semibold">
|
<h3 class="font-semibold">
|
||||||
{{ record.issuer.known ? record.issuer.displayName : "" }}
|
{{
|
||||||
|
record.giver.known ? record.giver.displayName : "Anonymous Giver"
|
||||||
|
}}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="ms-auto text-xs text-slate-500 italic">
|
<p class="ms-auto text-xs text-slate-500 italic">
|
||||||
{{ friendlyDate }}
|
{{ friendlyDate }}
|
||||||
@@ -37,16 +29,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a class="cursor-pointer" @click="$emit('loadClaim', record.jwtId)">
|
|
||||||
<font-awesome icon="circle-info" class="fa-fw text-slate-500" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-slate-100 rounded-b-md border border-slate-300 p-3 sm:p-4">
|
|
||||||
<!-- Record Image -->
|
<!-- Record Image -->
|
||||||
<div
|
<div
|
||||||
v-if="record.image"
|
v-if="record.image"
|
||||||
class="bg-cover mb-6 -mt-3 sm:-mt-4 -mx-3 sm:-mx-4"
|
class="bg-cover mb-6 -mx-3 sm:-mx-4"
|
||||||
:style="`background-image: url(${record.image});`"
|
:style="`background-image: url(${record.image});`"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@@ -68,41 +54,48 @@
|
|||||||
class="w-28 sm:w-40 text-center bg-white border border-slate-200 rounded p-2 sm:p-3"
|
class="w-28 sm:w-40 text-center bg-white border border-slate-200 rounded p-2 sm:p-3"
|
||||||
>
|
>
|
||||||
<div class="relative w-fit mx-auto">
|
<div class="relative w-fit mx-auto">
|
||||||
<div>
|
<template v-if="record.giver.profileImageUrl">
|
||||||
|
<EntityIcon
|
||||||
|
:profile-image-url="record.giver.profileImageUrl"
|
||||||
|
:class="[
|
||||||
|
!record.providerPlanName
|
||||||
|
? 'rounded-full size-[3rem] sm:size-[4rem] object-cover'
|
||||||
|
: 'rounded size-[3rem] sm:size-[4rem] object-cover',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
<!-- Project Icon -->
|
<!-- Project Icon -->
|
||||||
<div v-if="record.providerPlanName">
|
<template v-if="record.providerPlanName">
|
||||||
<ProjectIcon
|
<ProjectIcon
|
||||||
:entity-id="record.providerPlanName"
|
:entity-id="record.providerPlanName"
|
||||||
:icon-size="48"
|
:icon-size="48"
|
||||||
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
|
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</template>
|
||||||
<!-- Identicon for DIDs -->
|
<!-- Identicon for DIDs -->
|
||||||
<div v-else-if="record.agentDid">
|
<template v-else-if="record.giver.did">
|
||||||
<EntityIcon
|
<img
|
||||||
:entity-id="record.agentDid"
|
:src="`https://a.fsdn.com/con/app/proj/identicons/screenshots/225506.jpg`"
|
||||||
:profile-image-url="record.issuer.profileImageUrl"
|
class="rounded-full size-[3rem] sm:size-[4rem]"
|
||||||
class="rounded-full bg-slate-100 overflow-hidden !size-[3rem] sm:!size-[4rem]"
|
alt="Identicon"
|
||||||
/>
|
/>
|
||||||
</div>
|
</template>
|
||||||
<!-- Unknown Person -->
|
<!-- Unknown Person -->
|
||||||
<div v-else>
|
<template v-else>
|
||||||
<font-awesome
|
<fa
|
||||||
icon="person-circle-question"
|
icon="person-circle-question"
|
||||||
class="text-slate-300 text-[3rem] sm:text-[4rem]"
|
class="text-slate-300 text-[3rem] sm:text-[4rem]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="text-xs mt-2 line-clamp-3 sm:line-clamp-2">
|
||||||
v-if="record.providerPlanName || record.giver.known"
|
<fa
|
||||||
class="text-xs mt-2 line-clamp-3 sm:line-clamp-2"
|
:icon="record.providerPlanName ? 'building' : 'user'"
|
||||||
>
|
|
||||||
<font-awesome
|
|
||||||
:icon="record.providerPlanName ? 'users' : 'user'"
|
|
||||||
class="fa-fw text-slate-400"
|
class="fa-fw text-slate-400"
|
||||||
/>
|
/>
|
||||||
{{ record.providerPlanName || record.giver.displayName }}
|
{{ record.giver.displayName }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -130,41 +123,48 @@
|
|||||||
class="w-28 sm:w-40 text-center bg-white border border-slate-200 rounded p-2 sm:p-3"
|
class="w-28 sm:w-40 text-center bg-white border border-slate-200 rounded p-2 sm:p-3"
|
||||||
>
|
>
|
||||||
<div class="relative w-fit mx-auto">
|
<div class="relative w-fit mx-auto">
|
||||||
<div>
|
<template v-if="record.receiver.profileImageUrl">
|
||||||
|
<EntityIcon
|
||||||
|
:profile-image-url="record.receiver.profileImageUrl"
|
||||||
|
:class="[
|
||||||
|
!record.recipientProjectName
|
||||||
|
? 'rounded-full size-[3rem] sm:size-[4rem] object-cover'
|
||||||
|
: 'rounded size-[3rem] sm:size-[4rem] object-cover',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
<!-- Project Icon -->
|
<!-- Project Icon -->
|
||||||
<div v-if="record.recipientProjectName">
|
<template v-if="record.recipientProjectName">
|
||||||
<ProjectIcon
|
<ProjectIcon
|
||||||
:entity-id="record.recipientProjectName"
|
:entity-id="record.recipientProjectName"
|
||||||
:icon-size="48"
|
:icon-size="48"
|
||||||
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
|
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</template>
|
||||||
<!-- Identicon for DIDs -->
|
<!-- Identicon for DIDs -->
|
||||||
<div v-else-if="record.recipientDid">
|
<template v-else-if="record.receiver.did">
|
||||||
<EntityIcon
|
<img
|
||||||
:entity-id="record.recipientDid"
|
:src="`https://a.fsdn.com/con/app/proj/identicons/screenshots/225506.jpg`"
|
||||||
:profile-image-url="record.receiver.profileImageUrl"
|
class="rounded-full size-[3rem] sm:size-[4rem]"
|
||||||
class="rounded-full bg-slate-100 overflow-hidden !size-[3rem] sm:!size-[4rem]"
|
alt="Identicon"
|
||||||
/>
|
/>
|
||||||
</div>
|
</template>
|
||||||
<!-- Unknown Person -->
|
<!-- Unknown Person -->
|
||||||
<div v-else>
|
<template v-else>
|
||||||
<font-awesome
|
<fa
|
||||||
icon="person-circle-question"
|
icon="person-circle-question"
|
||||||
class="text-slate-300 text-[3rem] sm:text-[4rem]"
|
class="text-slate-300 text-[3rem] sm:text-[4rem]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="text-xs mt-2 line-clamp-3 sm:line-clamp-2">
|
||||||
v-if="record.recipientProjectName || record.receiver.known"
|
<fa
|
||||||
class="text-xs mt-2 line-clamp-3 sm:line-clamp-2"
|
:icon="record.recipientProjectName ? 'building' : 'user'"
|
||||||
>
|
|
||||||
<font-awesome
|
|
||||||
:icon="record.recipientProjectName ? 'users' : 'user'"
|
|
||||||
class="fa-fw text-slate-400"
|
class="fa-fw text-slate-400"
|
||||||
/>
|
/>
|
||||||
{{ record.recipientProjectName || record.receiver.displayName }}
|
{{ record.receiver.displayName }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -175,6 +175,15 @@
|
|||||||
{{ description }}
|
{{ description }}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
<p class="text-sm">{{ subDescription }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-2 text-lg bg-slate-300 rounded-b-md px-3 sm:px-4 py-1 sm:py-2"
|
||||||
|
>
|
||||||
|
<a class="cursor-pointer" @click="$emit('loadClaim', record.jwtId)">
|
||||||
|
<fa icon="circle-info" class="fa-fw text-slate-500" />
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
@@ -211,6 +220,53 @@ export default class ActivityListItem extends Vue {
|
|||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private formatParticipantInfo(): string {
|
||||||
|
const { giver, receiver } = this.record;
|
||||||
|
|
||||||
|
// Both participants are known contacts
|
||||||
|
if (giver.known && receiver.known) {
|
||||||
|
return `${giver.displayName} gave to ${receiver.displayName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only giver is known
|
||||||
|
if (giver.known) {
|
||||||
|
const recipient = this.record.recipientProjectName
|
||||||
|
? `the project "${this.record.recipientProjectName}"`
|
||||||
|
: receiver.displayName;
|
||||||
|
return `${giver.displayName} gave to ${recipient}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only receiver is known
|
||||||
|
if (receiver.known) {
|
||||||
|
const provider = this.record.providerPlanName
|
||||||
|
? `the project "${this.record.providerPlanName}"`
|
||||||
|
: giver.displayName;
|
||||||
|
return `${receiver.displayName} received from ${provider}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neither is known
|
||||||
|
return this.formatUnknownParticipants();
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatUnknownParticipants(): string {
|
||||||
|
const { giver, receiver, providerPlanName, recipientProjectName } =
|
||||||
|
this.record;
|
||||||
|
|
||||||
|
if (providerPlanName || recipientProjectName) {
|
||||||
|
const from = providerPlanName
|
||||||
|
? `the project "${providerPlanName}"`
|
||||||
|
: giver.displayName;
|
||||||
|
const to = recipientProjectName
|
||||||
|
? `the project "${recipientProjectName}"`
|
||||||
|
: receiver.displayName;
|
||||||
|
return `from ${from} to ${to}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return giver.displayName === receiver.displayName
|
||||||
|
? `between two who are ${giver.displayName}`
|
||||||
|
: `from ${giver.displayName} to ${receiver.displayName}`;
|
||||||
|
}
|
||||||
|
|
||||||
get description(): string {
|
get description(): string {
|
||||||
const claim =
|
const claim =
|
||||||
(this.record.fullClaim as unknown).claim || this.record.fullClaim;
|
(this.record.fullClaim as unknown).claim || this.record.fullClaim;
|
||||||
@@ -222,6 +278,12 @@ export default class ActivityListItem extends Vue {
|
|||||||
return `${claim.description}`;
|
return `${claim.description}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get subDescription(): string {
|
||||||
|
const participants = this.formatParticipantInfo();
|
||||||
|
|
||||||
|
return `${participants}`;
|
||||||
|
}
|
||||||
|
|
||||||
private displayAmount(code: string, amt: number) {
|
private displayAmount(code: string, amt: number) {
|
||||||
return `${amt} ${this.currencyShortWordForCode(code, amt === 1)}`;
|
return `${amt} ${this.currencyShortWordForCode(code, amt === 1)}`;
|
||||||
}
|
}
|
||||||
@@ -230,6 +292,11 @@ export default class ActivityListItem extends Vue {
|
|||||||
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
|
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get formattedTimestamp() {
|
||||||
|
// Add your timestamp formatting logic here
|
||||||
|
return this.record.timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
get canConfirm(): boolean {
|
get canConfirm(): boolean {
|
||||||
if (!this.isRegistered) return false;
|
if (!this.isRegistered) return false;
|
||||||
if (!isGiveClaimType(this.record.fullClaim?.["@type"])) return false;
|
if (!isGiveClaimType(this.record.fullClaim?.["@type"])) return false;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
:src="imageUrl"
|
:src="imageUrl"
|
||||||
class="max-h-[calc(100vh-5rem)] w-full h-full object-contain"
|
class="max-h-[calc(100vh-5rem)] w-full h-full object-contain"
|
||||||
alt="expanded shared content"
|
alt="expanded shared content"
|
||||||
@click="close"
|
@click.stop
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ db.on("populate", async () => {
|
|||||||
// ended up throwing lots of errors to the user... and they'd end up in a state
|
// ended up throwing lots of errors to the user... and they'd end up in a state
|
||||||
// where they couldn't take action because they couldn't unlock that identity.)
|
// where they couldn't take action because they couldn't unlock that identity.)
|
||||||
|
|
||||||
|
// check for the secret in storage
|
||||||
async function useSecretAndInitializeAccountsDB(
|
async function useSecretAndInitializeAccountsDB(
|
||||||
secretDB: SecretDexie,
|
secretDB: SecretDexie,
|
||||||
accountsDB: SensitiveDexie,
|
accountsDB: SensitiveDexie,
|
||||||
@@ -213,22 +214,6 @@ export async function updateAccountSettings(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logToDb(message: string): Promise<void> {
|
|
||||||
await db.open();
|
|
||||||
const todayKey = new Date().toDateString();
|
|
||||||
// only keep one day's worth of logs
|
|
||||||
const previous = await db.logs.get(todayKey);
|
|
||||||
if (!previous) {
|
|
||||||
// when this is today's first log, clear out everything previous
|
|
||||||
// to avoid the log table getting too large
|
|
||||||
// (let's limit a different way someday)
|
|
||||||
await db.logs.clear();
|
|
||||||
}
|
|
||||||
const prevMessages = (previous && previous.message) || "";
|
|
||||||
const fullMessage = `${prevMessages}\n${new Date().toISOString()} ${message}`;
|
|
||||||
await db.logs.update(todayKey, { message: fullMessage });
|
|
||||||
}
|
|
||||||
|
|
||||||
// similar method is in the sw_scripts/additional-scripts.js file
|
// similar method is in the sw_scripts/additional-scripts.js file
|
||||||
export async function logConsoleAndDb(
|
export async function logConsoleAndDb(
|
||||||
message: string,
|
message: string,
|
||||||
@@ -239,5 +224,16 @@ export async function logConsoleAndDb(
|
|||||||
} else {
|
} else {
|
||||||
logger.log(`${new Date().toISOString()} ${message}`);
|
logger.log(`${new Date().toISOString()} ${message}`);
|
||||||
}
|
}
|
||||||
await logToDb(message);
|
|
||||||
|
await db.open();
|
||||||
|
const todayKey = new Date().toDateString();
|
||||||
|
// only keep one day's worth of logs
|
||||||
|
const previous = await db.logs.get(todayKey);
|
||||||
|
if (!previous) {
|
||||||
|
// when this is today's first log, clear out everything previous
|
||||||
|
await db.logs.clear();
|
||||||
|
}
|
||||||
|
const prevMessages = (previous && previous.message) || "";
|
||||||
|
const fullMessage = `${prevMessages}\n${new Date().toISOString()} ${message}`;
|
||||||
|
await db.logs.update(todayKey, { message: fullMessage });
|
||||||
}
|
}
|
||||||
|
|||||||
37
src/electron/electron-logger.js
Normal file
37
src/electron/electron-logger.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Electron-specific logger implementation
|
||||||
|
*/
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const { app } = require("electron");
|
||||||
|
|
||||||
|
// Create logs directory if it doesn't exist
|
||||||
|
const logsDir = path.join(app.getPath("userData"), "logs");
|
||||||
|
if (!fs.existsSync(logsDir)) {
|
||||||
|
fs.mkdirSync(logsDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const logFile = path.join(
|
||||||
|
logsDir,
|
||||||
|
`electron-${new Date().toISOString().split("T")[0]}.log`,
|
||||||
|
);
|
||||||
|
|
||||||
|
function log(level, message) {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const logMessage = `[${timestamp}] [${level}] ${message}\n`;
|
||||||
|
|
||||||
|
// Write to log file
|
||||||
|
fs.appendFileSync(logFile, logMessage);
|
||||||
|
|
||||||
|
// Also output to console
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console[level.toLowerCase()](message);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
info: (message) => log("INFO", message),
|
||||||
|
warn: (message) => log("WARN", message),
|
||||||
|
error: (message) => log("ERROR", message),
|
||||||
|
debug: (message) => log("DEBUG", message),
|
||||||
|
getLogPath: () => logFile,
|
||||||
|
};
|
||||||
@@ -1,174 +1,236 @@
|
|||||||
const { app, BrowserWindow } = require("electron");
|
const { app, BrowserWindow, session, protocol, dialog } = require("electron");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const logger = require("../utils/logger");
|
|
||||||
|
|
||||||
// Check if running in dev mode
|
// Global window reference
|
||||||
const isDev = process.argv.includes("--inspect");
|
let mainWindow = null;
|
||||||
|
|
||||||
function createWindow() {
|
// Debug flags
|
||||||
// Add before createWindow function
|
const isDev = !app.isPackaged;
|
||||||
const preloadPath = path.join(__dirname, "preload.js");
|
|
||||||
logger.log("Checking preload path:", preloadPath);
|
|
||||||
logger.log("Preload exists:", fs.existsSync(preloadPath));
|
|
||||||
|
|
||||||
// Create the browser window.
|
// Helper for logging
|
||||||
const mainWindow = new BrowserWindow({
|
function logDebug(...args) {
|
||||||
width: 1200,
|
// eslint-disable-next-line no-console
|
||||||
height: 800,
|
console.log("[DEBUG]", ...args);
|
||||||
webPreferences: {
|
}
|
||||||
nodeIntegration: false,
|
|
||||||
contextIsolation: true,
|
|
||||||
webSecurity: true,
|
|
||||||
allowRunningInsecureContent: false,
|
|
||||||
preload: path.join(__dirname, "preload.js"),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Always open DevTools for now
|
function logError(...args) {
|
||||||
mainWindow.webContents.openDevTools();
|
// eslint-disable-next-line no-console
|
||||||
|
console.error("[ERROR]", ...args);
|
||||||
// Intercept requests to fix asset paths
|
if (!isDev && mainWindow) {
|
||||||
mainWindow.webContents.session.webRequest.onBeforeRequest(
|
dialog.showErrorBox("TimeSafari Error", args.join(" "));
|
||||||
{
|
|
||||||
urls: [
|
|
||||||
"file://*/*/assets/*",
|
|
||||||
"file://*/assets/*",
|
|
||||||
"file:///assets/*", // Catch absolute paths
|
|
||||||
"<all_urls>", // Catch all URLs as a fallback
|
|
||||||
],
|
|
||||||
},
|
|
||||||
(details, callback) => {
|
|
||||||
let url = details.url;
|
|
||||||
|
|
||||||
// Handle paths that don't start with file://
|
|
||||||
if (!url.startsWith("file://") && url.includes("/assets/")) {
|
|
||||||
url = `file://${path.join(__dirname, "www", url)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle absolute paths starting with /assets/
|
|
||||||
if (url.includes("/assets/") && !url.includes("/www/assets/")) {
|
|
||||||
const baseDir = url.includes("dist-electron")
|
|
||||||
? url.substring(
|
|
||||||
0,
|
|
||||||
url.indexOf("/dist-electron") + "/dist-electron".length,
|
|
||||||
)
|
|
||||||
: `file://${__dirname}`;
|
|
||||||
const assetPath = url.split("/assets/")[1];
|
|
||||||
const newUrl = `${baseDir}/www/assets/${assetPath}`;
|
|
||||||
callback({ redirectURL: newUrl });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback({}); // No redirect for other URLs
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isDev) {
|
|
||||||
// Debug info
|
|
||||||
logger.log("Debug Info:");
|
|
||||||
logger.log("Running in dev mode:", isDev);
|
|
||||||
logger.log("App is packaged:", app.isPackaged);
|
|
||||||
logger.log("Process resource path:", process.resourcesPath);
|
|
||||||
logger.log("App path:", app.getAppPath());
|
|
||||||
logger.log("__dirname:", __dirname);
|
|
||||||
logger.log("process.cwd():", process.cwd());
|
|
||||||
}
|
|
||||||
|
|
||||||
const indexPath = path.join(__dirname, "www", "index.html");
|
|
||||||
|
|
||||||
if (isDev) {
|
|
||||||
logger.log("Loading index from:", indexPath);
|
|
||||||
logger.log("www path:", path.join(__dirname, "www"));
|
|
||||||
logger.log("www assets path:", path.join(__dirname, "www", "assets"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(indexPath)) {
|
|
||||||
logger.error(`Index file not found at: ${indexPath}`);
|
|
||||||
throw new Error("Index file not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add CSP headers to allow API connections
|
|
||||||
mainWindow.webContents.session.webRequest.onHeadersReceived(
|
|
||||||
(details, callback) => {
|
|
||||||
callback({
|
|
||||||
responseHeaders: {
|
|
||||||
...details.responseHeaders,
|
|
||||||
"Content-Security-Policy": [
|
|
||||||
"default-src 'self';" +
|
|
||||||
"connect-src 'self' https://api.endorser.ch https://*.timesafari.app;" +
|
|
||||||
"img-src 'self' data: https: blob:;" +
|
|
||||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval';" +
|
|
||||||
"style-src 'self' 'unsafe-inline';" +
|
|
||||||
"font-src 'self' data:;",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Load the index.html
|
|
||||||
mainWindow
|
|
||||||
.loadFile(indexPath)
|
|
||||||
.then(() => {
|
|
||||||
logger.log("Successfully loaded index.html");
|
|
||||||
if (isDev) {
|
|
||||||
mainWindow.webContents.openDevTools();
|
|
||||||
logger.log("DevTools opened - running in dev mode");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
logger.error("Failed to load index.html:", err);
|
|
||||||
logger.error("Attempted path:", indexPath);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen for console messages from the renderer
|
|
||||||
mainWindow.webContents.on("console-message", (_event, level, message) => {
|
|
||||||
logger.log("Renderer Console:", message);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add right after creating the BrowserWindow
|
|
||||||
mainWindow.webContents.on(
|
|
||||||
"did-fail-load",
|
|
||||||
(event, errorCode, errorDescription) => {
|
|
||||||
logger.error("Page failed to load:", errorCode, errorDescription);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
mainWindow.webContents.on("preload-error", (event, preloadPath, error) => {
|
|
||||||
logger.error("Preload script error:", preloadPath, error);
|
|
||||||
});
|
|
||||||
|
|
||||||
mainWindow.webContents.on(
|
|
||||||
"console-message",
|
|
||||||
(event, level, message, line, sourceId) => {
|
|
||||||
logger.log("Renderer Console:", line, sourceId, message);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Enable remote debugging when in dev mode
|
|
||||||
if (isDev) {
|
|
||||||
mainWindow.webContents.openDevTools();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle app ready
|
// Get the most appropriate app path
|
||||||
app.whenReady().then(createWindow);
|
function getAppPath() {
|
||||||
|
if (app.isPackaged) {
|
||||||
|
const possiblePaths = [
|
||||||
|
path.join(process.resourcesPath, "app.asar", "dist-electron"),
|
||||||
|
path.join(process.resourcesPath, "app.asar"),
|
||||||
|
path.join(process.resourcesPath, "app"),
|
||||||
|
app.getAppPath(),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const testPath of possiblePaths) {
|
||||||
|
const testFile = path.join(testPath, "www", "index.html");
|
||||||
|
if (fs.existsSync(testFile)) {
|
||||||
|
logDebug(`Found valid app path: ${testPath}`);
|
||||||
|
return testPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logError("Could not find valid app path");
|
||||||
|
return path.join(process.resourcesPath, "app.asar"); // Default fallback
|
||||||
|
} else {
|
||||||
|
return __dirname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the browser window
|
||||||
|
function createWindow() {
|
||||||
|
logDebug("Creating window with paths:");
|
||||||
|
logDebug("- process.resourcesPath:", process.resourcesPath);
|
||||||
|
logDebug("- app.getAppPath():", app.getAppPath());
|
||||||
|
logDebug("- __dirname:", __dirname);
|
||||||
|
|
||||||
|
// Create the browser window
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
width: 1200,
|
||||||
|
height: 800,
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, "preload.js"),
|
||||||
|
contextIsolation: true,
|
||||||
|
nodeIntegration: false,
|
||||||
|
webSecurity: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fix root file paths - replaces all protocol handling
|
||||||
|
protocol.interceptFileProtocol("file", (request, callback) => {
|
||||||
|
let urlPath = request.url.substr(7); // Remove 'file://' prefix
|
||||||
|
urlPath = decodeURIComponent(urlPath); // Handle special characters
|
||||||
|
|
||||||
|
// Debug all asset requests
|
||||||
|
if (
|
||||||
|
urlPath.includes("assets/") ||
|
||||||
|
urlPath.endsWith(".js") ||
|
||||||
|
urlPath.endsWith(".css") ||
|
||||||
|
urlPath.endsWith(".html")
|
||||||
|
) {
|
||||||
|
logDebug(`Intercepted request for: ${urlPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix paths for files at root like registerSW.js or manifest.webmanifest
|
||||||
|
if (
|
||||||
|
urlPath.endsWith("registerSW.js") ||
|
||||||
|
urlPath.endsWith("manifest.webmanifest") ||
|
||||||
|
urlPath.endsWith("sw.js")
|
||||||
|
) {
|
||||||
|
const appBasePath = getAppPath();
|
||||||
|
const filePath = path.join(appBasePath, "www", path.basename(urlPath));
|
||||||
|
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
logDebug(`Serving ${urlPath} from ${filePath}`);
|
||||||
|
return callback({ path: filePath });
|
||||||
|
} else {
|
||||||
|
// For service worker, provide empty content to avoid errors
|
||||||
|
if (urlPath.endsWith("registerSW.js") || urlPath.endsWith("sw.js")) {
|
||||||
|
logDebug(`Providing empty SW file for ${urlPath}`);
|
||||||
|
// Create an empty JS file content that does nothing
|
||||||
|
const tempFile = path.join(
|
||||||
|
app.getPath("temp"),
|
||||||
|
path.basename(urlPath),
|
||||||
|
);
|
||||||
|
fs.writeFileSync(
|
||||||
|
tempFile,
|
||||||
|
"// Service workers disabled in Electron\n",
|
||||||
|
);
|
||||||
|
return callback({ path: tempFile });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle assets paths that might be requested from root
|
||||||
|
if (urlPath.startsWith("/assets/") || urlPath === "/assets") {
|
||||||
|
const appBasePath = getAppPath();
|
||||||
|
const filePath = path.join(appBasePath, "www", urlPath);
|
||||||
|
logDebug(`Redirecting ${urlPath} to ${filePath}`);
|
||||||
|
return callback({ path: filePath });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle assets paths that are missing the www folder
|
||||||
|
if (urlPath.includes("/assets/")) {
|
||||||
|
const appBasePath = getAppPath();
|
||||||
|
const relativePath = urlPath.substring(urlPath.indexOf("/assets/"));
|
||||||
|
const filePath = path.join(appBasePath, "www", relativePath);
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
logDebug(`Fixing asset path ${urlPath} to ${filePath}`);
|
||||||
|
return callback({ path: filePath });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For all other paths, just pass them through
|
||||||
|
callback({ path: urlPath });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up CSP headers - more permissive in dev mode
|
||||||
|
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||||||
|
callback({
|
||||||
|
responseHeaders: {
|
||||||
|
...details.responseHeaders,
|
||||||
|
"Content-Security-Policy": [
|
||||||
|
isDev
|
||||||
|
? "default-src 'self' file:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https://*; connect-src 'self' https://*"
|
||||||
|
: "default-src 'self' file:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https://image.timesafari.app https://*.americancloud.com; connect-src 'self' https://api.timesafari.app https://api.endorser.ch https://test-api.endorser.ch https://fonts.googleapis.com",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load the index.html with modifications
|
||||||
|
try {
|
||||||
|
const appPath = getAppPath();
|
||||||
|
const wwwFolder = path.join(appPath, "www");
|
||||||
|
const indexPath = path.join(wwwFolder, "index.html");
|
||||||
|
|
||||||
|
logDebug("Loading app from:", indexPath);
|
||||||
|
|
||||||
|
// Check if the file exists
|
||||||
|
if (fs.existsSync(indexPath)) {
|
||||||
|
// Read and modify index.html to disable service worker
|
||||||
|
let indexContent = fs.readFileSync(indexPath, "utf8");
|
||||||
|
|
||||||
|
// 1. Add base tag for proper path resolution
|
||||||
|
indexContent = indexContent.replace(
|
||||||
|
"<head>",
|
||||||
|
`<head>\n <base href="file://${wwwFolder}/">`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Disable service worker registration by replacing the script
|
||||||
|
if (indexContent.includes("registerSW.js")) {
|
||||||
|
indexContent = indexContent.replace(
|
||||||
|
/<script src="registerSW\.js"><\/script>/,
|
||||||
|
"<script>/* Service worker disabled in Electron */</script>",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a temp file with modified content
|
||||||
|
const tempDir = app.getPath("temp");
|
||||||
|
const tempIndexPath = path.join(tempDir, "timesafari-index.html");
|
||||||
|
fs.writeFileSync(tempIndexPath, indexContent);
|
||||||
|
|
||||||
|
// Load the modified index.html
|
||||||
|
mainWindow.loadFile(tempIndexPath).catch((err) => {
|
||||||
|
logError("Failed to load via loadFile:", err);
|
||||||
|
|
||||||
|
// Fallback to direct URL loading
|
||||||
|
mainWindow.loadURL(`file://${tempIndexPath}`).catch((err2) => {
|
||||||
|
logError("Both loading methods failed:", err2);
|
||||||
|
mainWindow.loadURL(
|
||||||
|
"data:text/html,<h1>Error: Failed to load TimeSafari</h1><p>Please contact support.</p>",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logError(`Index file not found at: ${indexPath}`);
|
||||||
|
mainWindow.loadURL(
|
||||||
|
"data:text/html,<h1>Error: Cannot find application</h1><p>index.html not found</p>",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logError("Failed to load app:", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open DevTools in development
|
||||||
|
if (isDev) {
|
||||||
|
mainWindow.webContents.openDevTools();
|
||||||
|
}
|
||||||
|
|
||||||
|
mainWindow.on("closed", () => {
|
||||||
|
mainWindow = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// App lifecycle events
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
logDebug(`Starting TimeSafari v${app.getVersion()}`);
|
||||||
|
|
||||||
|
// Skip the service worker registration for file:// protocol
|
||||||
|
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true";
|
||||||
|
|
||||||
|
createWindow();
|
||||||
|
|
||||||
|
app.on("activate", () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Handle all windows closed
|
|
||||||
app.on("window-all-closed", () => {
|
app.on("window-all-closed", () => {
|
||||||
if (process.platform !== "darwin") {
|
if (process.platform !== "darwin") app.quit();
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on("activate", () => {
|
// Handle uncaught exceptions
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
|
||||||
createWindow();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle any errors
|
|
||||||
process.on("uncaughtException", (error) => {
|
process.on("uncaughtException", (error) => {
|
||||||
logger.error("Uncaught Exception:", error);
|
logError("Uncaught Exception:", error);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,78 +1,95 @@
|
|||||||
const { contextBridge, ipcRenderer } = require("electron");
|
const { contextBridge, ipcRenderer } = require("electron");
|
||||||
|
|
||||||
const logger = {
|
// Safety wrapper for logging
|
||||||
log: (message, ...args) => {
|
function safeLog(message) {
|
||||||
if (process.env.NODE_ENV !== "production") {
|
try {
|
||||||
/* eslint-disable no-console */
|
// eslint-disable-next-line no-console
|
||||||
console.log(message, ...args);
|
console.log("[Preload]", message);
|
||||||
/* eslint-enable no-console */
|
} catch (e) {
|
||||||
}
|
// Silent fail for logging
|
||||||
},
|
|
||||||
warn: (message, ...args) => {
|
|
||||||
if (process.env.NODE_ENV !== "production") {
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
console.warn(message, ...args);
|
|
||||||
/* eslint-enable no-console */
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: (message, ...args) => {
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
console.error(message, ...args); // Errors should always be logged
|
|
||||||
/* eslint-enable no-console */
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use a more direct path resolution approach
|
|
||||||
const getPath = (pathType) => {
|
|
||||||
switch (pathType) {
|
|
||||||
case "userData":
|
|
||||||
return (
|
|
||||||
process.env.APPDATA ||
|
|
||||||
(process.platform === "darwin"
|
|
||||||
? `${process.env.HOME}/Library/Application Support`
|
|
||||||
: `${process.env.HOME}/.local/share`)
|
|
||||||
);
|
|
||||||
case "home":
|
|
||||||
return process.env.HOME;
|
|
||||||
case "appPath":
|
|
||||||
return process.resourcesPath;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
logger.log("Preload script starting...");
|
// Initialize
|
||||||
|
safeLog("Preload script starting...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
contextBridge.exposeInMainWorld("electronAPI", {
|
// Mock service worker registration to prevent errors
|
||||||
// Path utilities
|
if (window.navigator) {
|
||||||
getPath,
|
// Override the service worker registration to return a fake promise that resolves with nothing
|
||||||
|
window.navigator.serviceWorker = {
|
||||||
|
register: () => Promise.resolve({}),
|
||||||
|
getRegistration: () => Promise.resolve(null),
|
||||||
|
ready: Promise.resolve({}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// IPC functions
|
// Safely expose specific APIs to the renderer process
|
||||||
|
contextBridge.exposeInMainWorld("electronAPI", {
|
||||||
|
// Basic flags/info
|
||||||
|
isElectron: true,
|
||||||
|
|
||||||
|
// Disable service worker in Electron
|
||||||
|
disableServiceWorker: true,
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
log: (message) => {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log("[Renderer]", message);
|
||||||
|
} catch (e) {
|
||||||
|
// Silence any errors from logging
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Report errors to main process
|
||||||
|
reportError: (error) => {
|
||||||
|
try {
|
||||||
|
ipcRenderer.send("app-error", error.toString());
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error("Failed to report error to main process", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Safe path handling helper (no Node modules needed)
|
||||||
|
joinPath: (...parts) => {
|
||||||
|
return parts.join("/").replace(/\/\//g, "/");
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fix asset URLs
|
||||||
|
resolveAssetUrl: (assetPath) => {
|
||||||
|
if (assetPath.startsWith("/assets/")) {
|
||||||
|
return assetPath; // Already properly formed
|
||||||
|
}
|
||||||
|
if (assetPath.startsWith("assets/")) {
|
||||||
|
return "/" + assetPath; // Add leading slash
|
||||||
|
}
|
||||||
|
return assetPath;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Send messages to main process
|
||||||
send: (channel, data) => {
|
send: (channel, data) => {
|
||||||
const validChannels = ["toMain"];
|
// Whitelist channels for security
|
||||||
|
const validChannels = ["app-event", "log-event", "app-error"];
|
||||||
if (validChannels.includes(channel)) {
|
if (validChannels.includes(channel)) {
|
||||||
ipcRenderer.send(channel, data);
|
ipcRenderer.send(channel, data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Receive messages from main process
|
||||||
receive: (channel, func) => {
|
receive: (channel, func) => {
|
||||||
const validChannels = ["fromMain"];
|
const validChannels = ["app-notification", "log-response"];
|
||||||
if (validChannels.includes(channel)) {
|
if (validChannels.includes(channel)) {
|
||||||
ipcRenderer.on(channel, (event, ...args) => func(...args));
|
// Remove old listeners to avoid memory leaks
|
||||||
|
ipcRenderer.removeAllListeners(channel);
|
||||||
|
// Add the new listener
|
||||||
|
ipcRenderer.on(channel, (_, ...args) => func(...args));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Environment info
|
|
||||||
env: {
|
|
||||||
isElectron: true,
|
|
||||||
isDev: process.env.NODE_ENV === "development",
|
|
||||||
},
|
|
||||||
// Path utilities
|
|
||||||
getBasePath: () => {
|
|
||||||
return process.env.NODE_ENV === "development" ? "/" : "./";
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.log("Preload script completed successfully");
|
safeLog("Preload script completed successfully");
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
logger.error("Error in preload script:", error);
|
safeLog("Error in preload script: " + err.toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ import {
|
|||||||
faUser,
|
faUser,
|
||||||
faUsers,
|
faUsers,
|
||||||
faXmark,
|
faXmark,
|
||||||
|
faBuilding,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
@@ -167,6 +168,7 @@ library.add(
|
|||||||
faUser,
|
faUser,
|
||||||
faUsers,
|
faUsers,
|
||||||
faXmark,
|
faXmark,
|
||||||
|
faBuilding,
|
||||||
);
|
);
|
||||||
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||||
|
|||||||
@@ -37,3 +37,16 @@ if (
|
|||||||
"Service worker registration skipped - not enabled or not in production",
|
"Service worker registration skipped - not enabled or not in production",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function registerServiceWorker() {
|
||||||
|
// Skip service worker registration in Electron
|
||||||
|
if (window.electronAPI?.isElectron) {
|
||||||
|
console.log("Running in Electron - skipping service worker registration");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular service worker registration for web
|
||||||
|
if ("serviceWorker" in navigator) {
|
||||||
|
// ... existing code ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,11 @@
|
|||||||
import { GiveSummaryRecord, GiveVerifiableCredential } from "interfaces";
|
export interface GiveRecordWithContactInfo {
|
||||||
|
|
||||||
export interface GiveRecordWithContactInfo extends GiveSummaryRecord {
|
|
||||||
jwtId: string;
|
jwtId: string;
|
||||||
fullClaim: GiveVerifiableCredential;
|
fullClaim: unknown; // Replace with proper type
|
||||||
giver: {
|
giver: {
|
||||||
known: boolean;
|
known: boolean;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
profileImageUrl?: string;
|
profileImageUrl?: string;
|
||||||
};
|
};
|
||||||
issuer: {
|
|
||||||
known: boolean;
|
|
||||||
displayName: string;
|
|
||||||
profileImageUrl?: string;
|
|
||||||
};
|
|
||||||
receiver: {
|
receiver: {
|
||||||
known: boolean;
|
known: boolean;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
@@ -20,6 +13,8 @@ export interface GiveRecordWithContactInfo extends GiveSummaryRecord {
|
|||||||
};
|
};
|
||||||
providerPlanName?: string;
|
providerPlanName?: string;
|
||||||
recipientProjectName?: string;
|
recipientProjectName?: string;
|
||||||
description: string;
|
description?: string;
|
||||||
|
subDescription?: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
|
timestamp: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,18 @@
|
|||||||
import { logToDb } from "../db";
|
|
||||||
|
|
||||||
function safeStringify(obj: unknown) {
|
|
||||||
const seen = new WeakSet();
|
|
||||||
|
|
||||||
return JSON.stringify(obj, (key, value) => {
|
|
||||||
if (typeof value === "object" && value !== null) {
|
|
||||||
if (seen.has(value)) {
|
|
||||||
return "[Circular]";
|
|
||||||
}
|
|
||||||
seen.add(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === "function") {
|
|
||||||
return `[Function: ${value.name || "anonymous"}]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const logger = {
|
export const logger = {
|
||||||
log: (message: string, ...args: unknown[]) => {
|
log: (message: string, ...args: unknown[]) => {
|
||||||
if (process.env.NODE_ENV !== "production") {
|
if (process.env.NODE_ENV !== "production") {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(message, ...args);
|
console.log(message, ...args);
|
||||||
const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
|
|
||||||
logToDb(message + argsString);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
warn: (message: string, ...args: unknown[]) => {
|
warn: (message: string, ...args: unknown[]) => {
|
||||||
if (process.env.NODE_ENV !== "production") {
|
if (process.env.NODE_ENV !== "production") {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.warn(message, ...args);
|
console.warn(message, ...args);
|
||||||
const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
|
|
||||||
logToDb(message + argsString);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (message: string, ...args: unknown[]) => {
|
error: (message: string, ...args: unknown[]) => {
|
||||||
// Errors will always be logged
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(message, ...args);
|
console.error(message, ...args); // Errors should always be logged
|
||||||
const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
|
|
||||||
logToDb(message + argsString);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -196,14 +196,14 @@
|
|||||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md text-xs text-white"
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md text-xs text-white"
|
||||||
@click="openFeedFilters()"
|
@click="openFeedFilters()"
|
||||||
>
|
>
|
||||||
<font-awesome icon="filter" class="fa-fw" />
|
<fa icon="filter" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md text-xs text-white"
|
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md text-xs text-white"
|
||||||
@click="openFeedFilters()"
|
@click="openFeedFilters()"
|
||||||
>
|
>
|
||||||
<font-awesome icon="filter" class="fa-fw" />
|
<fa icon="filter" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -341,9 +341,25 @@ import {
|
|||||||
} from "../libs/util";
|
} from "../libs/util";
|
||||||
import { GiveSummaryRecord } from "../interfaces";
|
import { GiveSummaryRecord } from "../interfaces";
|
||||||
import * as serverUtil from "../libs/endorserServer";
|
import * as serverUtil from "../libs/endorserServer";
|
||||||
import { logger } from "../utils/logger";
|
// import { fa0 } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { GiveRecordWithContactInfo } from "types";
|
|
||||||
|
|
||||||
|
interface GiveRecordWithContactInfo extends GiveSummaryRecord {
|
||||||
|
jwtId: string;
|
||||||
|
giver: {
|
||||||
|
displayName: string;
|
||||||
|
known: boolean;
|
||||||
|
profileImageUrl?: string;
|
||||||
|
};
|
||||||
|
image?: string;
|
||||||
|
providerPlanName?: string;
|
||||||
|
recipientProjectName?: string;
|
||||||
|
receiver: {
|
||||||
|
displayName: string;
|
||||||
|
known: boolean;
|
||||||
|
profileImageUrl?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
/**
|
/**
|
||||||
* HomeView - Main view component for the application's home page
|
* HomeView - Main view component for the application's home page
|
||||||
*
|
*
|
||||||
@@ -805,12 +821,6 @@ export default class HomeView extends Vue {
|
|||||||
this.allMyDids,
|
this.allMyDids,
|
||||||
),
|
),
|
||||||
image: claim.image,
|
image: claim.image,
|
||||||
issuer: didInfoForContact(
|
|
||||||
record.issuerDid,
|
|
||||||
this.activeDid,
|
|
||||||
contactForDid(record.issuerDid, this.allContacts),
|
|
||||||
this.allMyDids,
|
|
||||||
),
|
|
||||||
providerPlanHandleId: provider?.identifier as string,
|
providerPlanHandleId: provider?.identifier as string,
|
||||||
providerPlanName: providedByPlan?.name as string,
|
providerPlanName: providedByPlan?.name as string,
|
||||||
recipientProjectName: fulfillsPlan?.name as string,
|
recipientProjectName: fulfillsPlan?.name as string,
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ web3>=6.0.0 # For Ethereum interaction
|
|||||||
eth-utils>=2.1.0 # For Ethereum utilities
|
eth-utils>=2.1.0 # For Ethereum utilities
|
||||||
pyjwt>=2.8.0 # For JWT operations
|
pyjwt>=2.8.0 # For JWT operations
|
||||||
cryptography>=42.0.0 # For key format conversion
|
cryptography>=42.0.0 # For key format conversion
|
||||||
jwcrypto
|
jwcrypto
|
||||||
|
setuptools
|
||||||
|
|||||||
@@ -24,6 +24,25 @@ export default defineConfig(async () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]
|
}],
|
||||||
|
build: {
|
||||||
|
outDir: 'dist-electron',
|
||||||
|
emptyOutDir: true,
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
vendor: ['vue', 'vue-router', 'pinia']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assetsDir: 'assets',
|
||||||
|
minify: 'terser',
|
||||||
|
terserOptions: {
|
||||||
|
compress: {
|
||||||
|
drop_console: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
base: './',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user