Compare commits
5 Commits
ai-context
...
fix-servic
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c515719e0 | ||
|
|
5bc7e93b4b | ||
|
|
ee6124021d | ||
|
|
ae25a066f2 | ||
|
|
4230deab1d |
16
.gitignore
vendored
@@ -38,18 +38,30 @@ pnpm-debug.log*
|
||||
/dist-capacitor/
|
||||
/test-playwright-results/
|
||||
playwright-tests
|
||||
test-playwright
|
||||
dist-electron-packages
|
||||
ios
|
||||
.ruby-version
|
||||
+.env
|
||||
|
||||
# Generated test files
|
||||
.generated/
|
||||
|
||||
# Fastlane
|
||||
ios/fastlane/report.xml
|
||||
ios/fastlane/Preview.html
|
||||
ios/fastlane/screenshots
|
||||
ios/fastlane/test_output
|
||||
android/fastlane/report.xml
|
||||
android/fastlane/Preview.html
|
||||
android/fastlane/screenshots
|
||||
android/fastlane/test_output
|
||||
.env.default
|
||||
vendor/
|
||||
|
||||
# Build logs
|
||||
build_logs/
|
||||
|
||||
# PWA icon files generated by capacitor-assets
|
||||
icons
|
||||
# Android generated assets
|
||||
android/app/src/main/assets/public/assets/
|
||||
|
||||
|
||||
110
BUILDING.md
@@ -9,19 +9,8 @@ For a quick dev environment setup, use [pkgx](https://pkgx.dev).
|
||||
- Node.js (LTS version recommended)
|
||||
- npm (comes with Node.js)
|
||||
- Git
|
||||
- For iOS builds: macOS with Xcode installed
|
||||
- For Android builds: Android Studio with SDK installed
|
||||
- For iOS builds: macOS with Xcode and ruby gems & bundle
|
||||
- pkgx +rubygems.org sh
|
||||
|
||||
- ... and you may have to fix these, especially with pkgx
|
||||
|
||||
```bash
|
||||
gem_path=$(which gem)
|
||||
shortened_path="${gem_path:h:h}"
|
||||
export GEM_HOME=$shortened_path
|
||||
export GEM_PATH=$shortened_path
|
||||
```
|
||||
|
||||
- For desktop builds: Additional build tools based on your OS
|
||||
|
||||
## Forks
|
||||
@@ -33,23 +22,26 @@ If you have forked this to make your own app, you'll want to customize the iOS &
|
||||
npx cap add ios
|
||||
```
|
||||
|
||||
You'll also want to edit the deep link configuration (see below).
|
||||
You'll also want to edit the deep link configuration.
|
||||
|
||||
## Initial Setup
|
||||
|
||||
Install dependencies:
|
||||
1. Clone the repository:
|
||||
|
||||
```bash
|
||||
git clone [repository-url]
|
||||
cd TimeSafari
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Web Dev Locally
|
||||
## Web Build
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Web Build for Server
|
||||
To build for web deployment:
|
||||
|
||||
1. Run the production build:
|
||||
|
||||
@@ -57,66 +49,17 @@ Install dependencies:
|
||||
npm run build
|
||||
```
|
||||
|
||||
The built files will be in the `dist` directory.
|
||||
2. The built files will be in the `dist` directory.
|
||||
|
||||
2. To test the production build locally:
|
||||
|
||||
You'll likely want to use test locations for the Endorser & image & partner servers; see "DEFAULT_ENDORSER_API_SERVER" & "DEFAULT_IMAGE_API_SERVER" & "DEFAULT_PARTNER_API_SERVER" below.
|
||||
3. To test the production build locally:
|
||||
|
||||
```bash
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compile and minify for test & production
|
||||
|
||||
* If there are DB changes: before updating the test server, open browser(s) with current version to test DB migrations.
|
||||
|
||||
* `npx prettier --write ./sw_scripts/`
|
||||
|
||||
* Update the ClickUp tasks & CHANGELOG.md & the version in package.json, run `npm install`.
|
||||
|
||||
* Commit everything (since the commit hash is used the app).
|
||||
|
||||
* Put the commit hash in the changelog (which will help you remember to bump the version later).
|
||||
|
||||
* Tag with the new version, [online](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases) or `git tag 0.3.55 && git push origin 0.3.55`.
|
||||
|
||||
* For test, build the app (because test server is not yet set up to build):
|
||||
|
||||
```bash
|
||||
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_PASSKEYS_ENABLED=true npm run build
|
||||
```
|
||||
|
||||
... and transfer to the test server:
|
||||
|
||||
```bash
|
||||
rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safari
|
||||
```
|
||||
|
||||
(Let's replace that with a .env.development or .env.staging file.)
|
||||
|
||||
(Note: The test BVC_MEETUPS_PROJECT_CLAIM_ID does not resolve as a URL because it's only in the test DB and the prod redirect won't redirect there.)
|
||||
|
||||
* For prod, get on the server and run the correct build:
|
||||
|
||||
... and log onto the server:
|
||||
|
||||
* `pkgx +npm sh`
|
||||
|
||||
* `cd crowd-funder-for-time-pwa && git checkout master && git pull && git checkout 0.3.55 && npm install && npm run build && cd -`
|
||||
|
||||
(The plain `npm run build` uses the .env.production file.)
|
||||
|
||||
* Back up the time-safari/dist folder & deploy: `mv time-safari/dist time-safari-dist-prev.0 && mv crowd-funder-for-time-pwa/dist time-safari/`
|
||||
|
||||
* Record the new hash in the changelog. Edit package.json to increment version & add "-beta", `npm install`, and commit. Also record what version is on production.
|
||||
|
||||
|
||||
|
||||
|
||||
## Desktop Build (Electron)
|
||||
|
||||
### Linux Build
|
||||
### Building for Linux
|
||||
|
||||
1. Build the electron app in production mode:
|
||||
|
||||
@@ -187,11 +130,7 @@ Prerequisites: macOS with Xcode installed
|
||||
3. Copy the assets:
|
||||
|
||||
```bash
|
||||
# It makes no sense why capacitor-assets will not run without these but it actually changes the contents.
|
||||
mkdir -p ios/App/App/Assets.xcassets/AppIcon.appiconset
|
||||
echo '{"images":[]}' > ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json
|
||||
mkdir -p ios/App/App/Assets.xcassets/Splash.imageset
|
||||
echo '{"images":[]}' > ios/App/App/Assets.xcassets/Splash.imageset/Contents.json
|
||||
npx capacitor-assets generate --ios
|
||||
```
|
||||
|
||||
@@ -203,12 +142,6 @@ Prerequisites: macOS with Xcode installed
|
||||
|
||||
4. Use Xcode to build and run on simulator or device.
|
||||
|
||||
#### First-time iOS Configuration
|
||||
|
||||
- Generate certificates inside XCode.
|
||||
|
||||
- Right-click on App and under Signing & Capabilities set the Team.
|
||||
|
||||
### Android Build
|
||||
|
||||
Prerequisites: Android Studio with SDK installed
|
||||
@@ -241,7 +174,7 @@ Prerequisites: Android Studio with SDK installed
|
||||
|
||||
5. Use Android Studio to build and run on emulator or device.
|
||||
|
||||
## Android Build from the console
|
||||
## Building Android from the console
|
||||
|
||||
```bash
|
||||
cd android
|
||||
@@ -254,18 +187,11 @@ Prerequisites: Android Studio with SDK installed
|
||||
... or, to create the `aab` file, `bundle` instead of `build`:
|
||||
|
||||
```bash
|
||||
./gradlew bundleDebug -Dlint.baselines.continue=true
|
||||
```
|
||||
|
||||
... or, to create a signed release, add the app/gradle.properties.secrets file (see properties at top of app/build.gradle) and the app/time-safari-upload-key-pkcs12.jks file, then `bundleRelease`:
|
||||
|
||||
```bash
|
||||
./gradlew bundleRelease -Dlint.baselines.continue=true
|
||||
./gradlew bundle -Dlint.baselines.continue=true
|
||||
```
|
||||
|
||||
|
||||
|
||||
## First-time Android Configuration for deep links
|
||||
## Configuring Android for deep links
|
||||
|
||||
You must add the following intent filter to the `android/app/src/main/AndroidManifest.xml` file:
|
||||
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
# Time Safari Context
|
||||
|
||||
## Project Overview
|
||||
|
||||
Time Safari is an application designed to foster community building through gifts, gratitude, and collaborative projects. The app should make it extremely easy and intuitive for users of any age and capability to recognize contributions, build trust networks, and organize collective action. It is built on services that preserve privacy and data sovereignty.
|
||||
|
||||
The ultimate goals of Time Safari are two-fold:
|
||||
|
||||
1. **Connect** Make it easy, rewarding, and non-threatening for people to connect with others who have similar interests, and to initiate activities together. This helps people accomplish and learn from other individuals in less-structured environments; moreover, it helps them discover who they want to continue to support and with whom they want to maintain relationships.
|
||||
|
||||
2. **Reveal** Widely advertise the great support and rewards that are being given and accepted freely, especially non-monetary ones. Using visuals and text, display the kind of impact that gifts are making in the lives of others. Also show useful and engaging reports of project statistics and personal accomplishments.
|
||||
|
||||
|
||||
## Core Approaches
|
||||
|
||||
Time Safari should help everyday users build meaningful connections and organize collective efforts by:
|
||||
|
||||
1. **Recognizing Contributions**: Creating permanent, verifiable records of gifts and contributions people give to each other and their communities.
|
||||
|
||||
2. **Facilitating Collaboration**: Making it ridiculously easy for people to ask for or propose help on projects and interests that matter to them.
|
||||
|
||||
3. **Building Trust Networks**: Enabling users to maintain their network and activity visibility. Developing reputation through verified contributions and references, which can be selectively shown to others outside the network.
|
||||
|
||||
4. **Preserving Privacy**: Ensuring personal identifiers are only shared with explicitly authorized contacts, allowing private individuals including children to participate safely.
|
||||
|
||||
5. **Engaging Content**: Displaying people's records in compelling stories, and highlighting those projects that are lifting people's lives long-term, both in physical support and in emotional-spiritual-creative thriving.
|
||||
|
||||
|
||||
## Technical Foundation
|
||||
|
||||
This application is built on a privacy-preserving claims architecture (via endorser.ch) with these key characteristics:
|
||||
|
||||
- **Decentralized Identifiers (DIDs)**: User identities are based on public/private key pairs stored on their devices
|
||||
- **Cryptographic Verification**: All claims and confirmations are cryptographically signed
|
||||
- **User-Controlled Visibility**: Users explicitly control who can see their identifiers and data
|
||||
- **Merkle-Chained Claims**: Claims are cryptographically chained for verification and integrity
|
||||
- **Native and Web App**: Works on iOS, Android, and web browsers
|
||||
|
||||
|
||||
## User Journey
|
||||
|
||||
The typical progression of usage follows these stages:
|
||||
|
||||
1. **Gratitude & Recognition**: Users begin by expressing and recording gratitude for gifts received, building a foundation of acknowledgment.
|
||||
|
||||
2. **Project Proposals**: Users propose projects and ideas, reaching out to connect with others who share similar interests.
|
||||
|
||||
3. **Action Triggers**: Offers of help serve as triggers and motivations to execute proposed projects, moving from ideas to action.
|
||||
|
||||
|
||||
## Context for LLM Development
|
||||
|
||||
When developing new functionality for Time Safari, consider these design principles:
|
||||
|
||||
1. **Accessibility First**: Features should be usable by non-technical users with minimal learning curve.
|
||||
|
||||
2. **Privacy by Design**: All features must respect user privacy and data sovereignty.
|
||||
|
||||
3. **Progressive Enhancement**: Core functionality should work across all devices, with richer experiences where supported.
|
||||
|
||||
4. **Voluntary Collaboration**: The system should enable but never coerce participation.
|
||||
|
||||
5. **Trust Building**: Features should help build verifiable trust between users.
|
||||
|
||||
6. **Network Effects**: Consider how features scale as more users join the platform.
|
||||
|
||||
7. **Low Resource Requirements**: The system should be lightweight enough to run on inexpensive devices users already own.
|
||||
|
||||
|
||||
## Use Cases to Support
|
||||
|
||||
LLM development should focus on enhancing these key use cases:
|
||||
|
||||
1. **Community Building**: Tools that help people find others with shared interests and values.
|
||||
|
||||
2. **Project Coordination**: Features that make it easy to propose collaborative projects and to submit suggestions and offers to existing ones.
|
||||
|
||||
3. **Reputation Building**: Methods for users to showcase their contributions and reliability, in contexts where they explicitly reveal that information.
|
||||
|
||||
4. **Governance Experimentation**: Features that facilitate decision-making and collective governance.
|
||||
|
||||
|
||||
## Constraints
|
||||
|
||||
When developing new features, be mindful of these constraints:
|
||||
|
||||
1. **Privacy Preservation**: User identifiers must remain private except when explicitly shared.
|
||||
|
||||
2. **Platform Limitations**: Features must work within the constraints of the target app platforms, while aiming to leverage the best platform technology available.
|
||||
|
||||
3. **Endorser API Limitations**: Backend features are constrained by the endorser.ch API capabilities.
|
||||
|
||||
4. **Performance on Low-End Devices**: The application should remain performant on older/simpler devices.
|
||||
|
||||
5. **Offline-First When Possible**: Key functionality should work offline when feasible.
|
||||
|
||||
53
README.md
@@ -19,6 +19,59 @@ npm run dev
|
||||
|
||||
See [BUILDING.md](BUILDING.md) for more details.
|
||||
|
||||
See the test locations for "IMAGE_API_SERVER" or "PARTNER_API_SERVER" below, or use http://localhost:3000 for local endorser.ch
|
||||
|
||||
|
||||
### Run all UI tests
|
||||
|
||||
Look at [BUILDING.md](BUILDING.md) for the "test-all" instructions and [TESTING.md](test-playwright/TESTING.md) for more details.
|
||||
|
||||
|
||||
### Compile and minify for test & production
|
||||
|
||||
* If there are DB changes: before updating the test server, open browser(s) with current version to test DB migrations.
|
||||
|
||||
* `npx prettier --write ./sw_scripts/`
|
||||
|
||||
* Update the ClickUp tasks & CHANGELOG.md & the version in package.json, run `npm install`.
|
||||
|
||||
* Commit everything (since the commit hash is used the app).
|
||||
|
||||
* Put the commit hash in the changelog (which will help you remember to bump the version later).
|
||||
|
||||
* Tag with the new version, [online](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases) or `git tag 0.3.55 && git push origin 0.3.55`.
|
||||
|
||||
* For test, build the app (because test server is not yet set up to build):
|
||||
|
||||
```bash
|
||||
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_PASSKEYS_ENABLED=true npm run build
|
||||
```
|
||||
|
||||
... and transfer to the test server:
|
||||
|
||||
```bash
|
||||
rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safari
|
||||
```
|
||||
|
||||
(Let's replace that with a .env.development or .env.staging file.)
|
||||
|
||||
(Note: The test BVC_MEETUPS_PROJECT_CLAIM_ID does not resolve as a URL because it's only in the test DB and the prod redirect won't redirect there.)
|
||||
|
||||
* For prod, get on the server and run the correct build:
|
||||
|
||||
... and log onto the server:
|
||||
|
||||
* `pkgx +npm sh`
|
||||
|
||||
* `cd crowd-funder-for-time-pwa && git checkout master && git pull && git checkout 0.3.55 && npm install && npm run build && cd -`
|
||||
|
||||
(The plain `npm run build` uses the .env.production file.)
|
||||
|
||||
* Back up the time-safari/dist folder & deploy: `mv time-safari/dist time-safari-dist-prev.0 && mv crowd-funder-for-time-pwa/dist time-safari/`
|
||||
|
||||
* Record the new hash in the changelog. Edit package.json to increment version & add "-beta", `npm install`, and commit. Also record what version is on production.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
8
android/.gitignore
vendored
@@ -1,8 +1,5 @@
|
||||
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
|
||||
|
||||
app/gradle.properties.secrets
|
||||
app/time-safari-upload-key-pkcs12.jks
|
||||
|
||||
# Built application files
|
||||
*.apk
|
||||
*.aar
|
||||
@@ -102,8 +99,3 @@ app/src/main/assets/public
|
||||
app/src/main/assets/capacitor.config.json
|
||||
app/src/main/assets/capacitor.plugins.json
|
||||
app/src/main/res/xml/config.xml
|
||||
|
||||
# Generated Icons from capacitor-assets
|
||||
app/src/main/res/drawable/*.png
|
||||
app/src/main/res/drawable-*/*.png
|
||||
app/src/main/res/mipmap-*/*.png
|
||||
|
||||
@@ -1,38 +1,14 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
// These are sample values to set in gradle.properties.secrets
|
||||
// MY_KEYSTORE_FILE=time-safari-upload-key-pkcs12.jks
|
||||
// MY_KEYSTORE_PASSWORD=...
|
||||
// MY_KEY_ALIAS=time-safari-key-alias
|
||||
// MY_KEY_PASSWORD=...
|
||||
|
||||
// Try to load from environment variables first
|
||||
project.ext.MY_KEYSTORE_FILE = System.getenv('ANDROID_KEYSTORE_FILE') ?: ""
|
||||
project.ext.MY_KEYSTORE_PASSWORD = System.getenv('ANDROID_KEYSTORE_PASSWORD') ?: ""
|
||||
project.ext.MY_KEY_ALIAS = System.getenv('ANDROID_KEY_ALIAS') ?: ""
|
||||
project.ext.MY_KEY_PASSWORD = System.getenv('ANDROID_KEY_PASSWORD') ?: ""
|
||||
|
||||
// If no environment variables, try to load from secrets file
|
||||
if (!project.ext.MY_KEYSTORE_FILE) {
|
||||
def secretsPropertiesFile = rootProject.file("gradle.properties.secrets")
|
||||
if (secretsPropertiesFile.exists()) {
|
||||
Properties secretsProperties = new Properties()
|
||||
secretsProperties.load(new FileInputStream(secretsPropertiesFile))
|
||||
secretsProperties.each { name, value ->
|
||||
project.ext[name] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'app.timesafari'
|
||||
compileSdk rootProject.ext.compileSdkVersion
|
||||
defaultConfig {
|
||||
applicationId "app.timesafari.app"
|
||||
applicationId "app.timesafari"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 9
|
||||
versionName "0.4.4"
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
@@ -40,41 +16,10 @@ android {
|
||||
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
if (project.ext.MY_KEYSTORE_FILE &&
|
||||
project.ext.MY_KEYSTORE_PASSWORD &&
|
||||
project.ext.MY_KEY_ALIAS &&
|
||||
project.ext.MY_KEY_PASSWORD) {
|
||||
|
||||
storeFile file(project.ext.MY_KEYSTORE_FILE)
|
||||
storePassword project.ext.MY_KEYSTORE_PASSWORD
|
||||
keyAlias project.ext.MY_KEY_ALIAS
|
||||
keyPassword project.ext.MY_KEY_PASSWORD
|
||||
}
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
// Only sign if we have the signing config
|
||||
if (signingConfigs.release.storeFile != null) {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable bundle builds (without which it doesn't work right for bundleDebug vs bundleRelease)
|
||||
bundle {
|
||||
language {
|
||||
enableSplit = true
|
||||
}
|
||||
density {
|
||||
enableSplit = true
|
||||
}
|
||||
abi {
|
||||
enableSplit = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,6 @@ public class ExampleInstrumentedTest {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
|
||||
assertEquals("app.timesafari.app", appContext.getPackageName());
|
||||
assertEquals("app.timesafari", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"appId": "app.timesafari.app",
|
||||
"appId": "app.timesafari",
|
||||
"appName": "TimeSafari",
|
||||
"webDir": "dist",
|
||||
"bundledWebRuntime": false,
|
||||
|
||||
BIN
android/app/src/main/res/drawable-land-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
android/app/src/main/res/drawable-land-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
android/app/src/main/res/drawable-land-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
android/app/src/main/res/drawable-land-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
android/app/src/main/res/drawable-land-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
android/app/src/main/res/drawable-port-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
android/app/src/main/res/drawable-port-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
android/app/src/main/res/drawable-port-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
android/app/src/main/res/drawable-port-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
android/app/src/main/res/drawable-port-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
android/app/src/main/res/drawable/splash.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
@@ -1,9 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background>
|
||||
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
|
||||
</background>
|
||||
<foreground>
|
||||
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
|
||||
</foreground>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,9 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background>
|
||||
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
|
||||
</background>
|
||||
<foreground>
|
||||
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
|
||||
</foreground>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
assets/icon-only.png
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
assets/icon.png
|
Before Width: | Height: | Size: 99 KiB |
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"]
|
||||
}
|
||||
}
|
||||
27
ios/.gitignore
vendored
@@ -1,27 +0,0 @@
|
||||
App/build
|
||||
App/Pods
|
||||
App/output
|
||||
App/App/public
|
||||
DerivedData
|
||||
xcuserdata
|
||||
*.xcuserstate
|
||||
|
||||
# Cordova plugins for Capacitor
|
||||
capacitor-cordova-ios-plugins
|
||||
|
||||
# Generated Config files
|
||||
App/App/capacitor.config.json
|
||||
App/App/config.xml
|
||||
|
||||
# User-specific Xcode files
|
||||
App/App.xcodeproj/xcuserdata/*.xcuserdatad/
|
||||
App/App.xcodeproj/*.xcuserstate
|
||||
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
|
||||
# Generated Icons from capacitor-assets (also Contents.json which is confusing; see BUILDING.md)
|
||||
App/App/Assets.xcassets/AppIcon.appiconset
|
||||
App/App/Assets.xcassets/Splash.imageset
|
||||
10
ios/App/App.xcworkspace/contents.xcworkspacedata
generated
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Time Safari.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,49 +0,0 @@
|
||||
import UIKit
|
||||
import Capacitor
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationWillResignActive(_ application: UIApplication) {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
}
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
||||
// Called when the app was launched with a url. Feel free to add additional processing here,
|
||||
// but if you want the App API to support tracking app url opens, make sure to keep this call
|
||||
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
|
||||
// Called when the app was launched with an activity, including Universal Links.
|
||||
// Feel free to add additional processing here, but if you want the App API to support
|
||||
// tracking app url opens, make sure to keep this call
|
||||
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17132" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17105"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<imageView key="view" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Splash" id="snD-IY-ifK">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
</imageView>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="Splash" width="1366" height="1366"/>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14111" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Bridge View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="CAPBridgeViewController" customModule="Capacitor" sceneMemberID="viewController"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>TimeSafari</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,24 +0,0 @@
|
||||
require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers'
|
||||
|
||||
platform :ios, '13.0'
|
||||
use_frameworks!
|
||||
|
||||
# workaround to avoid Xcode caching of Pods that requires
|
||||
# Product -> Clean Build Folder after new Cordova plugins installed
|
||||
# Requires CocoaPods 1.6 or newer
|
||||
install! 'cocoapods', :disable_input_output_paths => true
|
||||
|
||||
def capacitor_pods
|
||||
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
||||
end
|
||||
|
||||
target 'App' do
|
||||
capacitor_pods
|
||||
# Add your Pods here
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
assertDeploymentTarget(installer)
|
||||
end
|
||||
@@ -1,28 +0,0 @@
|
||||
PODS:
|
||||
- Capacitor (6.2.0):
|
||||
- CapacitorCordova
|
||||
- CapacitorApp (6.0.2):
|
||||
- Capacitor
|
||||
- CapacitorCordova (6.2.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- "Capacitor (from `../../node_modules/@capacitor/ios`)"
|
||||
- "CapacitorApp (from `../../node_modules/@capacitor/app`)"
|
||||
- "CapacitorCordova (from `../../node_modules/@capacitor/ios`)"
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Capacitor:
|
||||
:path: "../../node_modules/@capacitor/ios"
|
||||
CapacitorApp:
|
||||
:path: "../../node_modules/@capacitor/app"
|
||||
CapacitorCordova:
|
||||
:path: "../../node_modules/@capacitor/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Capacitor: 05d35014f4425b0740fc8776481f6a369ad071bf
|
||||
CapacitorApp: e1e6b7d05e444d593ca16fd6d76f2b7c48b5aea7
|
||||
CapacitorCordova: b33e7f4aa4ed105dd43283acdd940964374a87d9
|
||||
|
||||
PODFILE CHECKSUM: 4233f5c5f414604460ff96d372542c311b0fb7a8
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
@@ -1,414 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 48;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };
|
||||
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; };
|
||||
504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; };
|
||||
504EC30D1FED79650016851F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30B1FED79650016851F /* Main.storyboard */; };
|
||||
504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; };
|
||||
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
|
||||
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
|
||||
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = "<group>"; };
|
||||
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
|
||||
504EC3041FED79650016851F /* Time Safari.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Time Safari.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
|
||||
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = "<group>"; };
|
||||
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
504EC3011FED79650016851F /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
504EC2FB1FED79650016851F = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
504EC3061FED79650016851F /* App */,
|
||||
504EC3051FED79650016851F /* Products */,
|
||||
7F8756D8B27F46E3366F6CEA /* Pods */,
|
||||
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
504EC3051FED79650016851F /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
504EC3041FED79650016851F /* Time Safari.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
504EC3061FED79650016851F /* App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
50379B222058CBB4000EE86E /* capacitor.config.json */,
|
||||
504EC3071FED79650016851F /* AppDelegate.swift */,
|
||||
504EC30B1FED79650016851F /* Main.storyboard */,
|
||||
504EC30E1FED79650016851F /* Assets.xcassets */,
|
||||
504EC3101FED79650016851F /* LaunchScreen.storyboard */,
|
||||
504EC3131FED79650016851F /* Info.plist */,
|
||||
2FAD9762203C412B000D30F8 /* config.xml */,
|
||||
50B271D01FEDC1A000F3C39B /* public */,
|
||||
);
|
||||
path = App;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7F8756D8B27F46E3366F6CEA /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */,
|
||||
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
504EC3031FED79650016851F /* Time Safari */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "Time Safari" */;
|
||||
buildPhases = (
|
||||
6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */,
|
||||
504EC3001FED79650016851F /* Sources */,
|
||||
504EC3011FED79650016851F /* Frameworks */,
|
||||
504EC3021FED79650016851F /* Resources */,
|
||||
9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Time Safari";
|
||||
productName = App;
|
||||
productReference = 504EC3041FED79650016851F /* Time Safari.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
504EC2FC1FED79650016851F /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 0920;
|
||||
TargetAttributes = {
|
||||
504EC3031FED79650016851F = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
LastSwiftMigration = 1100;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "Time Safari" */;
|
||||
compatibilityVersion = "Xcode 8.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 504EC2FB1FED79650016851F;
|
||||
packageReferences = (
|
||||
);
|
||||
productRefGroup = 504EC3051FED79650016851F /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
504EC3031FED79650016851F /* Time Safari */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
504EC3021FED79650016851F /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */,
|
||||
50B271D11FEDC1A000F3C39B /* public in Resources */,
|
||||
504EC30F1FED79650016851F /* Assets.xcassets in Resources */,
|
||||
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */,
|
||||
504EC30D1FED79650016851F /* Main.storyboard in Resources */,
|
||||
2FAD9763203C412B000D30F8 /* config.xml in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-App-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App/Pods-App-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
504EC3001FED79650016851F /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
504EC30B1FED79650016851F /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
504EC30C1FED79650016851F /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
504EC3101FED79650016851F /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
504EC3111FED79650016851F /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
504EC3141FED79650016851F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
504EC3151FED79650016851F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
504EC3171FED79650016851F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Time Safari";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
504EC3181FED79650016851F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Time Safari";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
504EC2FF1FED79650016851F /* Build configuration list for PBXProject "Time Safari" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
504EC3141FED79650016851F /* Debug */,
|
||||
504EC3151FED79650016851F /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "Time Safari" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
504EC3171FED79650016851F /* Debug */,
|
||||
504EC3181FED79650016851F /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 504EC2FC1FED79650016851F /* Project object */;
|
||||
}
|
||||
517
package-lock.json
generated
60
package.json
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "timesafari",
|
||||
"version": "0.4.4",
|
||||
"description": "Time Safari Application",
|
||||
"description": "TimeSafari Desktop Application",
|
||||
"author": {
|
||||
"name": "Time Safari Team"
|
||||
"name": "TimeSafari Team"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite --config vite.config.dev.mts",
|
||||
"serve": "vite preview",
|
||||
"serve": "NODE_ENV=production vite preview --mode production --host",
|
||||
"build": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.mts",
|
||||
"lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src",
|
||||
"lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src",
|
||||
@@ -22,14 +22,14 @@
|
||||
"check:ios-device": "xcrun xctrace list devices 2>&1 | grep -w 'Booted' || (echo 'No iOS simulator running' && exit 1)",
|
||||
"clean:electron": "rimraf dist-electron",
|
||||
"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: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:build-linux": "npm run build:electron && electron-builder --linux AppImage",
|
||||
"electron:build-linux-deb": "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": "npm run check:electron && npm run build:electron && electron-builder --linux AppImage",
|
||||
"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 check:electron &&npm run build:electron && electron-builder --linux AppImage",
|
||||
"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: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:release": "cd ios && fastlane release",
|
||||
"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": {
|
||||
"@capacitor/android": "^6.2.0",
|
||||
@@ -155,30 +158,33 @@
|
||||
},
|
||||
"main": "./dist-electron/main.js",
|
||||
"build": {
|
||||
"appId": "app.timesafari.app",
|
||||
"appId": "app.timesafari",
|
||||
"productName": "TimeSafari",
|
||||
"directories": {
|
||||
"output": "dist-electron-packages"
|
||||
},
|
||||
"files": [
|
||||
"dist-electron/**/*",
|
||||
"src/electron/**/*",
|
||||
"main.js"
|
||||
"!dist-electron/node_modules/**/*"
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "dist-electron",
|
||||
"to": "."
|
||||
}
|
||||
"directories": {
|
||||
"output": "dist-electron-packages",
|
||||
"buildResources": "build-resources"
|
||||
},
|
||||
"extraResources": [],
|
||||
"asar": true,
|
||||
"asarUnpack": [
|
||||
"dist-electron/www/assets/**/*"
|
||||
],
|
||||
"linux": {
|
||||
"target": [
|
||||
"AppImage",
|
||||
"deb"
|
||||
],
|
||||
"category": "Office",
|
||||
"icon": "build/icon.png"
|
||||
"target": ["AppImage"],
|
||||
"category": "Utility",
|
||||
"executableName": "TimeSafari"
|
||||
},
|
||||
"asar": true
|
||||
"mac": {
|
||||
"category": "public.app-category.productivity"
|
||||
},
|
||||
"win": {
|
||||
"target": ["nsis"]
|
||||
},
|
||||
"artifactName": "TimeSafari-${version}-${arch}.${ext}",
|
||||
"publish": null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,21 +46,21 @@ export default defineConfig({
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
// {
|
||||
// name: 'chromium-serial',
|
||||
// testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
||||
// use: {
|
||||
// ...devices['Desktop Chrome'],
|
||||
// permissions: ["clipboard-read"],
|
||||
// },
|
||||
// workers: 1, // Force serial execution for problematic tests
|
||||
// },
|
||||
// {
|
||||
// name: 'firefox-serial',
|
||||
// testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
||||
// use: { ...devices['Desktop Firefox'] },
|
||||
// workers: 1,
|
||||
// },
|
||||
{
|
||||
name: 'chromium-serial',
|
||||
testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
permissions: ["clipboard-read"],
|
||||
},
|
||||
workers: 1, // Force serial execution for problematic tests
|
||||
},
|
||||
{
|
||||
name: 'firefox-serial',
|
||||
testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
workers: 1,
|
||||
},
|
||||
{
|
||||
name: 'chromium',
|
||||
testMatch: /^(?!.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts).+\.spec\.ts$/,
|
||||
@@ -76,26 +76,32 @@ export default defineConfig({
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: "Mobile Chrome",
|
||||
// use: { ...devices["Pixel 5"] },
|
||||
// },
|
||||
// {
|
||||
// name: "Mobile Safari",
|
||||
// use: { ...devices["iPhone 12"] },
|
||||
// },
|
||||
|
||||
{
|
||||
name: "Mobile Chrome",
|
||||
use: { ...devices["Pixel 5"] },
|
||||
},
|
||||
{
|
||||
name: "Mobile Safari",
|
||||
use: { ...devices["iPhone 12"] },
|
||||
},
|
||||
|
||||
/* Test against branded browsers. */
|
||||
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
{
|
||||
name: "Google Chrome",
|
||||
use: { ...devices["Desktop Chrome"], channel: "chrome" },
|
||||
},
|
||||
],
|
||||
|
||||
/* Configure global timeout; default is 30000 milliseconds */
|
||||
// the image upload will often not succeed in 5 seconds
|
||||
// 33-record-gift-x10.spec.ts:90:5 > Record 9 new gifts will often not succeed in 30 seconds
|
||||
timeout: 45000, // various tests fail at various times with 25000
|
||||
timeout: 35000, // various tests fail at various times with 25000
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
/**
|
||||
|
||||
@@ -88,6 +88,12 @@ async function main() {
|
||||
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!');
|
||||
} catch (error) {
|
||||
console.error('Build failed:', error);
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -51,7 +51,7 @@ const { existsSync } = require('fs');
|
||||
*/
|
||||
function checkCommand(command, errorMessage) {
|
||||
try {
|
||||
execSync(command, { stdio: 'ignore' });
|
||||
execSync(command + ' --version', { stdio: 'ignore' });
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(`❌ ${errorMessage}`);
|
||||
@@ -164,10 +164,10 @@ function main() {
|
||||
|
||||
// Check required command line tools
|
||||
// These are essential for building and testing the application
|
||||
success &= checkCommand('node --version', 'Node.js is required');
|
||||
success &= checkCommand('npm --version', 'npm is required');
|
||||
success &= checkCommand('gradle --version', 'Gradle is required for Android builds');
|
||||
success &= checkCommand('xcodebuild --help', 'Xcode is required for iOS builds');
|
||||
success &= checkCommand('node', 'Node.js is required');
|
||||
success &= checkCommand('npm', 'npm is required');
|
||||
success &= checkCommand('gradle', 'Gradle is required for Android builds');
|
||||
success &= checkCommand('xcodebuild', 'Xcode is required for iOS builds');
|
||||
|
||||
// Check platform-specific development environments
|
||||
success &= checkAndroidSetup();
|
||||
|
||||
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
@@ -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
|
||||
};
|
||||
@@ -170,7 +170,7 @@ const executeDeeplink = async (url, description, log) => {
|
||||
|
||||
try {
|
||||
// Stop the app before executing the deep link
|
||||
execSync('adb shell am force-stop app.timesafari.app');
|
||||
execSync('adb shell am force-stop app.timesafari');
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1s
|
||||
|
||||
execSync(`adb shell am start -W -a android.intent.action.VIEW -d "${url}" -c android.intent.category.BROWSABLE`);
|
||||
|
||||
@@ -10,10 +10,8 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
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">
|
||||
<div class="bg-slate-100 rounded-t-md border border-slate-300 p-3 sm:p-4">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<div v-if="record.issuerDid">
|
||||
<EntityIcon
|
||||
:entity-id="record.issuerDid"
|
||||
@@ -37,16 +35,10 @@
|
||||
</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 -->
|
||||
<div
|
||||
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});`"
|
||||
>
|
||||
<a
|
||||
@@ -62,12 +54,10 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative flex justify-between gap-4 max-w-[40rem] mx-auto mb-5"
|
||||
>
|
||||
<div class="relative flex justify-between gap-4 max-w-lg mx-auto mb-5">
|
||||
<!-- Source -->
|
||||
<div
|
||||
class="w-[8rem] sm:w-[12rem] 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>
|
||||
@@ -96,23 +86,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="record.providerPlanName || record.giver.known"
|
||||
class="text-xs mt-2 truncate"
|
||||
>
|
||||
<font-awesome
|
||||
:icon="record.providerPlanName ? 'users' : 'user'"
|
||||
class="fa-fw text-slate-400"
|
||||
/>
|
||||
{{ record.providerPlanName || record.giver.displayName }}
|
||||
<div class="text-xs mt-2 line-clamp-3 sm:line-clamp-2">
|
||||
<div v-if="record.providerPlanName || record.giver.known">
|
||||
<font-awesome
|
||||
:icon="record.providerPlanName ? 'users' : 'user'"
|
||||
class="fa-fw text-slate-400"
|
||||
/>
|
||||
{{ record.providerPlanName || record.giver.displayName }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Arrow -->
|
||||
<div
|
||||
class="absolute inset-x-[8rem] sm:inset-x-[12rem] mx-2 top-1/2 -translate-y-1/2"
|
||||
class="absolute inset-x-28 sm:inset-x-40 mx-2 top-1/2 -translate-y-1/2"
|
||||
>
|
||||
<div class="text-sm text-center leading-none font-semibold pe-[15px]">
|
||||
<div class="text-sm text-center leading-none font-semibold">
|
||||
{{ fetchAmount }}
|
||||
</div>
|
||||
|
||||
@@ -129,7 +118,7 @@
|
||||
|
||||
<!-- Destination -->
|
||||
<div
|
||||
class="w-[8rem] sm:w-[12rem] 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>
|
||||
@@ -158,15 +147,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="record.recipientProjectName || record.receiver.known"
|
||||
class="text-xs mt-2 truncate"
|
||||
>
|
||||
<font-awesome
|
||||
:icon="record.recipientProjectName ? 'users' : 'user'"
|
||||
class="fa-fw text-slate-400"
|
||||
/>
|
||||
{{ record.recipientProjectName || record.receiver.displayName }}
|
||||
<div class="text-xs mt-2 line-clamp-3 sm:line-clamp-2">
|
||||
<div v-if="record.recipientProjectName || record.receiver.known">
|
||||
<font-awesome
|
||||
:icon="record.recipientProjectName ? 'users' : 'user'"
|
||||
class="fa-fw text-slate-400"
|
||||
/>
|
||||
{{ record.recipientProjectName || record.receiver.displayName }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -178,6 +166,14 @@
|
||||
</a>
|
||||
</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)">
|
||||
<font-awesome icon="circle-info" class="fa-fw text-slate-500" />
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
@@ -217,6 +213,10 @@ export default class ActivityListItem extends Vue {
|
||||
const claim =
|
||||
(this.record.fullClaim as unknown).claim || this.record.fullClaim;
|
||||
|
||||
if (!claim.description) {
|
||||
return "something not described";
|
||||
}
|
||||
|
||||
return `${claim.description}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/** * @file InfiniteScroll.vue * @description A Vue component that implements
|
||||
infinite scrolling functionality using the Intersection Observer API. * This
|
||||
component emits a 'reached-bottom' event when the user scrolls near the bottom
|
||||
of the content. * It includes debouncing to prevent multiple rapid triggers and
|
||||
loading state management. * * @author Matthew Raymer * @version 1.0.0 */
|
||||
|
||||
<template>
|
||||
<div ref="scrollContainer">
|
||||
<slot />
|
||||
@@ -14,51 +8,13 @@ loading state management. * * @author Matthew Raymer * @version 1.0.0 */
|
||||
<script lang="ts">
|
||||
import { Component, Emit, Prop, Vue } from "vue-facing-decorator";
|
||||
|
||||
/**
|
||||
* InfiniteScroll Component
|
||||
*
|
||||
* This component implements infinite scrolling functionality by observing when a user
|
||||
* scrolls near the bottom of the content. It uses the Intersection Observer API for
|
||||
* efficient scroll detection and includes debouncing to prevent multiple rapid triggers.
|
||||
*
|
||||
* Usage in template:
|
||||
* ```vue
|
||||
* <InfiniteScroll @reached-bottom="loadMore">
|
||||
* <div>Content goes here</div>
|
||||
* </InfiniteScroll>
|
||||
* ```
|
||||
*
|
||||
* Props:
|
||||
* - distance: number (default: 200) - Distance in pixels from the bottom at which to trigger the event
|
||||
*
|
||||
* Events:
|
||||
* - reached-bottom: Emitted when the user scrolls near the bottom of the content
|
||||
*/
|
||||
@Component
|
||||
export default class InfiniteScroll extends Vue {
|
||||
/** Distance in pixels from the bottom at which to trigger the reached-bottom event */
|
||||
@Prop({ default: 200 })
|
||||
readonly distance!: number;
|
||||
|
||||
/** Intersection Observer instance for detecting scroll position */
|
||||
private observer!: IntersectionObserver;
|
||||
|
||||
/** Flag to track initial render state */
|
||||
private isInitialRender = true;
|
||||
|
||||
/** Flag to prevent multiple simultaneous loading states */
|
||||
private isLoading = false;
|
||||
|
||||
/** Timeout ID for debouncing scroll events */
|
||||
private debounceTimeout: number | null = null;
|
||||
|
||||
/**
|
||||
* Vue lifecycle hook that runs after component updates.
|
||||
* Initializes the Intersection Observer if not already set up.
|
||||
*
|
||||
* @internal
|
||||
* Used internally by Vue's lifecycle system
|
||||
*/
|
||||
updated() {
|
||||
if (!this.observer) {
|
||||
const options = {
|
||||
@@ -74,50 +30,18 @@ export default class InfiniteScroll extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue lifecycle hook that runs before component unmounting.
|
||||
* Cleans up the Intersection Observer and any pending timeouts.
|
||||
*
|
||||
* @internal
|
||||
* Used internally by Vue's lifecycle system
|
||||
*/
|
||||
// 'beforeUnmount' hook runs before unmounting the component
|
||||
beforeUnmount() {
|
||||
if (this.observer) {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
if (this.debounceTimeout) {
|
||||
window.clearTimeout(this.debounceTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles intersection observer callbacks when the sentinel element becomes visible.
|
||||
* Implements debouncing to prevent multiple rapid triggers and manages loading state.
|
||||
*
|
||||
* @param entries - Array of IntersectionObserverEntry objects
|
||||
* @returns false (required by @Emit decorator)
|
||||
*
|
||||
* @internal
|
||||
* Used internally by the Intersection Observer
|
||||
* @emits reached-bottom - Emitted when the user scrolls near the bottom
|
||||
*/
|
||||
@Emit("reached-bottom")
|
||||
handleIntersection(entries: IntersectionObserverEntry[]) {
|
||||
const entry = entries[0];
|
||||
if (entry.isIntersecting && !this.isLoading) {
|
||||
// Debounce the intersection event
|
||||
if (this.debounceTimeout) {
|
||||
window.clearTimeout(this.debounceTimeout);
|
||||
}
|
||||
|
||||
this.debounceTimeout = window.setTimeout(() => {
|
||||
this.isLoading = true;
|
||||
this.$emit("reached-bottom", true);
|
||||
// Reset loading state after a short delay
|
||||
setTimeout(() => {
|
||||
this.isLoading = false;
|
||||
}, 1000);
|
||||
}, 300);
|
||||
if (entry.isIntersecting) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
</p>
|
||||
|
||||
<p class="mt-4">
|
||||
Search for a topic, or search around your neighborhood under "Nearby".
|
||||
Search for a topic, or search around your neighborhod under "Nearby".
|
||||
</p>
|
||||
|
||||
<p class="mt-4">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<!-- QUICK NAV -->
|
||||
<nav id="QuickNav" class="fixed bottom-0 left-0 right-0 bg-slate-200 z-50">
|
||||
<ul class="flex text-2xl px-6 py-2 gap-1 max-w-3xl mx-auto">
|
||||
<ul class="flex text-2xl p-2 gap-2 max-w-3xl mx-auto">
|
||||
<!-- Home Feed -->
|
||||
<li
|
||||
:class="{
|
||||
@@ -52,7 +52,7 @@
|
||||
>
|
||||
<div class="flex flex-col items-center">
|
||||
<font-awesome icon="hand" class="fa-fw" />
|
||||
<span class="text-xs mt-1">yours</span>
|
||||
<span class="text-xs mt-1">your work</span>
|
||||
</div>
|
||||
</router-link>
|
||||
</li>
|
||||
|
||||
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 fs = require("fs");
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
// Check if running in dev mode
|
||||
const isDev = process.argv.includes("--inspect");
|
||||
// Global window reference
|
||||
let mainWindow = null;
|
||||
|
||||
function createWindow() {
|
||||
// Add before createWindow function
|
||||
const preloadPath = path.join(__dirname, "preload.js");
|
||||
logger.log("Checking preload path:", preloadPath);
|
||||
logger.log("Preload exists:", fs.existsSync(preloadPath));
|
||||
// Debug flags
|
||||
const isDev = !app.isPackaged;
|
||||
|
||||
// Create the browser window.
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
webSecurity: true,
|
||||
allowRunningInsecureContent: false,
|
||||
preload: path.join(__dirname, "preload.js"),
|
||||
},
|
||||
});
|
||||
// Helper for logging
|
||||
function logDebug(...args) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("[DEBUG]", ...args);
|
||||
}
|
||||
|
||||
// Always open DevTools for now
|
||||
mainWindow.webContents.openDevTools();
|
||||
|
||||
// Intercept requests to fix asset paths
|
||||
mainWindow.webContents.session.webRequest.onBeforeRequest(
|
||||
{
|
||||
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();
|
||||
function logError(...args) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("[ERROR]", ...args);
|
||||
if (!isDev && mainWindow) {
|
||||
dialog.showErrorBox("TimeSafari Error", args.join(" "));
|
||||
}
|
||||
}
|
||||
|
||||
// Handle app ready
|
||||
app.whenReady().then(createWindow);
|
||||
// Get the most appropriate app path
|
||||
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", () => {
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
}
|
||||
if (process.platform !== "darwin") app.quit();
|
||||
});
|
||||
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle any errors
|
||||
// Handle uncaught exceptions
|
||||
process.on("uncaughtException", (error) => {
|
||||
logger.error("Uncaught Exception:", error);
|
||||
logError("Uncaught Exception:", error);
|
||||
});
|
||||
|
||||
@@ -1,78 +1,95 @@
|
||||
const { contextBridge, ipcRenderer } = require("electron");
|
||||
|
||||
const logger = {
|
||||
log: (message, ...args) => {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
/* eslint-disable no-console */
|
||||
console.log(message, ...args);
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
},
|
||||
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 "";
|
||||
// Safety wrapper for logging
|
||||
function safeLog(message) {
|
||||
try {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("[Preload]", message);
|
||||
} catch (e) {
|
||||
// Silent fail for logging
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
logger.log("Preload script starting...");
|
||||
// Initialize
|
||||
safeLog("Preload script starting...");
|
||||
|
||||
try {
|
||||
contextBridge.exposeInMainWorld("electronAPI", {
|
||||
// Path utilities
|
||||
getPath,
|
||||
// Mock service worker registration to prevent errors
|
||||
if (window.navigator) {
|
||||
// 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) => {
|
||||
const validChannels = ["toMain"];
|
||||
// Whitelist channels for security
|
||||
const validChannels = ["app-event", "log-event", "app-error"];
|
||||
if (validChannels.includes(channel)) {
|
||||
ipcRenderer.send(channel, data);
|
||||
}
|
||||
},
|
||||
|
||||
// Receive messages from main process
|
||||
receive: (channel, func) => {
|
||||
const validChannels = ["fromMain"];
|
||||
const validChannels = ["app-notification", "log-response"];
|
||||
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");
|
||||
} catch (error) {
|
||||
logger.error("Error in preload script:", error);
|
||||
safeLog("Preload script completed successfully");
|
||||
} catch (err) {
|
||||
safeLog("Error in preload script: " + err.toString());
|
||||
}
|
||||
|
||||
@@ -2,38 +2,66 @@
|
||||
|
||||
import { register } from "register-service-worker";
|
||||
|
||||
// Only register service worker if explicitly enabled and in production
|
||||
// Add debug logging for environment variables
|
||||
console.log('[ServiceWorker] Environment variables:', {
|
||||
VITE_PWA_ENABLED: process.env.VITE_PWA_ENABLED,
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
BASE_URL: process.env.BASE_URL,
|
||||
CAN_REGISTER: process.env.VITE_PWA_ENABLED === "true" && process.env.NODE_ENV === "production"
|
||||
});
|
||||
|
||||
// Modified condition to handle both string and boolean true
|
||||
if (
|
||||
process.env.VITE_PWA_ENABLED === "true" &&
|
||||
process.env.NODE_ENV === "production"
|
||||
) {
|
||||
register(`${process.env.BASE_URL}sw.js`, {
|
||||
console.log('[ServiceWorker] Attempting to register service worker...');
|
||||
// Use '/' as fallback if BASE_URL is undefined
|
||||
const baseUrl = process.env.BASE_URL || '/';
|
||||
register(`${baseUrl}sw.js`, {
|
||||
ready() {
|
||||
console.log("Service worker is active.");
|
||||
console.log("[ServiceWorker] Service worker is active.");
|
||||
},
|
||||
registered() {
|
||||
console.log("Service worker has been registered.");
|
||||
registered(registration) {
|
||||
console.log("[ServiceWorker] Service worker has been registered:", registration);
|
||||
},
|
||||
cached() {
|
||||
console.log("Content has been cached for offline use.");
|
||||
cached(registration) {
|
||||
console.log("[ServiceWorker] Content has been cached for offline use:", registration);
|
||||
},
|
||||
updatefound() {
|
||||
console.log("New content is downloading.");
|
||||
updatefound(registration) {
|
||||
console.log("[ServiceWorker] New content is downloading:", registration);
|
||||
},
|
||||
updated() {
|
||||
console.log("New content is available; please refresh.");
|
||||
updated(registration) {
|
||||
console.log("[ServiceWorker] New content is available:", registration);
|
||||
},
|
||||
offline() {
|
||||
console.log(
|
||||
"No internet connection found. App is running in offline mode.",
|
||||
);
|
||||
console.log("[ServiceWorker] No internet connection found. App is running in offline mode.");
|
||||
},
|
||||
error(error) {
|
||||
console.error("Error during service worker registration:", error);
|
||||
console.error("[ServiceWorker] Error during service worker registration:", error);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.log(
|
||||
"Service worker registration skipped - not enabled or not in production",
|
||||
"[ServiceWorker] Registration skipped:",
|
||||
{
|
||||
enabled: process.env.VITE_PWA_ENABLED === "true",
|
||||
production: process.env.NODE_ENV === "production",
|
||||
value: process.env.VITE_PWA_ENABLED,
|
||||
type: typeof process.env.VITE_PWA_ENABLED
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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 ...
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@
|
||||
<span class="mb-2 font-bold">Location for Searches</span>
|
||||
<router-link
|
||||
:to="{ name: 'search-area' }"
|
||||
class="text-m bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md mb-2"
|
||||
class="text-m bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2"
|
||||
>
|
||||
{{ isSearchAreasSet ? "Change" : "Set" }} Search Area…
|
||||
</router-link>
|
||||
@@ -471,7 +471,7 @@
|
||||
<!-- id used by puppeteer test script -->
|
||||
<h3
|
||||
id="advanced"
|
||||
class="text-blue-500 text-sm font-semibold mb-3"
|
||||
class="text-sm uppercase font-semibold mb-3"
|
||||
@click="showAdvanced = !showAdvanced"
|
||||
>
|
||||
Advanced
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
<button
|
||||
v-if="!showGiveNumbers"
|
||||
href=""
|
||||
class="text-md bg-gradient-to-b shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-3 px-3 py-1.5 rounded-md"
|
||||
class="text-md bg-gradient-to-b shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-2 px-1 py-1 rounded-md"
|
||||
:style="
|
||||
contactsSelected.length > 0
|
||||
? 'background-image: linear-gradient(to bottom, #3b82f6, #1e40af);'
|
||||
@@ -130,7 +130,7 @@
|
||||
<div class="w-full text-right">
|
||||
<button
|
||||
href=""
|
||||
class="text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 rounded-md"
|
||||
class="text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 py-1 rounded-md"
|
||||
@click="toggleShowContactAmounts()"
|
||||
>
|
||||
{{
|
||||
@@ -180,7 +180,14 @@
|
||||
data-testId="contactListItem"
|
||||
>
|
||||
<div class="grow overflow-hidden">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center">
|
||||
<EntityIcon
|
||||
:contact="contact"
|
||||
:icon-size="24"
|
||||
class="inline-block align-text-bottom border border-slate-300 rounded cursor-pointer"
|
||||
@click="showLargeIdenticon = contact"
|
||||
/>
|
||||
|
||||
<input
|
||||
v-if="!showGiveNumbers"
|
||||
type="checkbox"
|
||||
@@ -197,19 +204,14 @@
|
||||
"
|
||||
/>
|
||||
|
||||
<EntityIcon
|
||||
:contact="contact"
|
||||
:icon-size="48"
|
||||
class="inline-block align-text-bottom border border-slate-300 rounded cursor-pointer overflow-hidden"
|
||||
@click="showLargeIdenticon = contact"
|
||||
/>
|
||||
|
||||
<h2 class="text-base font-semibold w-1/3 truncate flex-shrink-0">
|
||||
<h2
|
||||
class="text-base font-semibold ml-2 w-1/3 truncate flex-shrink-0"
|
||||
>
|
||||
{{ contactNameNonBreakingSpace(contact.name) }}
|
||||
</h2>
|
||||
|
||||
<span>
|
||||
<div class="flex gap-2 items-center">
|
||||
<div class="flex items-center">
|
||||
<router-link
|
||||
:to="{
|
||||
path: '/did/' + encodeURIComponent(contact.did),
|
||||
@@ -218,79 +220,81 @@
|
||||
>
|
||||
<font-awesome
|
||||
icon="circle-info"
|
||||
class="text-xl text-blue-500"
|
||||
class="text-xl text-blue-500 ml-4"
|
||||
/>
|
||||
</router-link>
|
||||
|
||||
<span class="text-sm overflow-hidden">{{
|
||||
<span class="ml-4 text-sm overflow-hidden">{{
|
||||
libsUtil.shortDid(contact.did)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<div class="ml-4 text-sm">
|
||||
{{ contact.notes }}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="showGiveNumbers && contact.did != activeDid"
|
||||
class="ml-auto flex gap-1.5 mt-2"
|
||||
>
|
||||
<button
|
||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-l-md"
|
||||
:title="givenToMeDescriptions[contact.did] || ''"
|
||||
@click="confirmShowGiftedDialog(contact.did, activeDid)"
|
||||
<div id="ContactActions" class="flex gap-1.5 mt-2">
|
||||
<div
|
||||
v-if="showGiveNumbers && contact.did != activeDid"
|
||||
class="ml-auto flex gap-1.5"
|
||||
>
|
||||
From:
|
||||
<br />
|
||||
{{
|
||||
/* eslint-disable prettier/prettier */
|
||||
showGiveTotals
|
||||
? ((givenToMeConfirmed[contact.did] || 0)
|
||||
+ (givenToMeUnconfirmed[contact.did] || 0))
|
||||
: showGiveConfirmed
|
||||
? (givenToMeConfirmed[contact.did] || 0)
|
||||
: (givenToMeUnconfirmed[contact.did] || 0)
|
||||
/* eslint-enable prettier/prettier */
|
||||
}}
|
||||
</button>
|
||||
<button
|
||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-l-md"
|
||||
:title="givenToMeDescriptions[contact.did] || ''"
|
||||
@click="confirmShowGiftedDialog(contact.did, activeDid)"
|
||||
>
|
||||
From:
|
||||
<br />
|
||||
{{
|
||||
/* eslint-disable prettier/prettier */
|
||||
showGiveTotals
|
||||
? ((givenToMeConfirmed[contact.did] || 0)
|
||||
+ (givenToMeUnconfirmed[contact.did] || 0))
|
||||
: showGiveConfirmed
|
||||
? (givenToMeConfirmed[contact.did] || 0)
|
||||
: (givenToMeUnconfirmed[contact.did] || 0)
|
||||
/* eslint-enable prettier/prettier */
|
||||
}}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white -ml-1.5 px-2 py-1.5 rounded-r-md border-l"
|
||||
:title="givenByMeDescriptions[contact.did] || ''"
|
||||
@click="confirmShowGiftedDialog(activeDid, contact.did)"
|
||||
>
|
||||
To:
|
||||
<br />
|
||||
{{
|
||||
/* eslint-disable prettier/prettier */
|
||||
showGiveTotals
|
||||
? ((givenByMeConfirmed[contact.did] || 0)
|
||||
+ (givenByMeUnconfirmed[contact.did] || 0))
|
||||
: showGiveConfirmed
|
||||
? (givenByMeConfirmed[contact.did] || 0)
|
||||
: (givenByMeUnconfirmed[contact.did] || 0)
|
||||
/* eslint-enable prettier/prettier */
|
||||
}}
|
||||
</button>
|
||||
<button
|
||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white -ml-1.5 px-2 py-1.5 rounded-r-md border-l"
|
||||
:title="givenByMeDescriptions[contact.did] || ''"
|
||||
@click="confirmShowGiftedDialog(activeDid, contact.did)"
|
||||
>
|
||||
To:
|
||||
<br />
|
||||
{{
|
||||
/* eslint-disable prettier/prettier */
|
||||
showGiveTotals
|
||||
? ((givenByMeConfirmed[contact.did] || 0)
|
||||
+ (givenByMeUnconfirmed[contact.did] || 0))
|
||||
: showGiveConfirmed
|
||||
? (givenByMeConfirmed[contact.did] || 0)
|
||||
: (givenByMeUnconfirmed[contact.did] || 0)
|
||||
/* eslint-enable prettier/prettier */
|
||||
}}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-md border border-blue-400"
|
||||
data-testId="offerButton"
|
||||
@click="openOfferDialog(contact.did, contact.name)"
|
||||
>
|
||||
Offer
|
||||
</button>
|
||||
<button
|
||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-md border border-blue-400"
|
||||
data-testId="offerButton"
|
||||
@click="openOfferDialog(contact.did, contact.name)"
|
||||
>
|
||||
Offer
|
||||
</button>
|
||||
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'contact-amounts',
|
||||
query: { contactDid: contact.did },
|
||||
}"
|
||||
class="text-sm bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-md border border-slate-400"
|
||||
title="See more given activity"
|
||||
>
|
||||
<fa icon="file-lines" class="fa-fw" />
|
||||
</router-link>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'contact-amounts',
|
||||
query: { contactDid: contact.did },
|
||||
}"
|
||||
class="text-sm bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-md border border-slate-400"
|
||||
title="See more given activity"
|
||||
>
|
||||
<font-awesome icon="file-lines" class="fa-fw" />
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@@ -313,7 +317,7 @@
|
||||
<button
|
||||
v-if="!showGiveNumbers"
|
||||
href=""
|
||||
class="text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-3 px-3 py-1.5 rounded-md"
|
||||
class="text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-2 px-1 py-1 rounded-md"
|
||||
:style="
|
||||
contactsSelected.length > 0
|
||||
? 'background-image: linear-gradient(to bottom, #3b82f6, #1e40af);'
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
/>
|
||||
|
||||
<h3
|
||||
class="text-blue-500 text-sm font-semibold mb-3"
|
||||
class="text-sm uppercase font-semibold mb-3"
|
||||
@click="showAdvanced = !showAdvanced"
|
||||
>
|
||||
Advanced
|
||||
|
||||
@@ -266,7 +266,7 @@
|
||||
<div class="text-center">
|
||||
<button
|
||||
data-testId="offerButton"
|
||||
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 text-sm leading-tight rounded-md"
|
||||
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 rounded-md"
|
||||
@click="openOfferDialog()"
|
||||
>
|
||||
Offer to this (maybe with conditions)...
|
||||
@@ -353,7 +353,7 @@
|
||||
<div v-if="activeDid && isRegistered">
|
||||
<div class="text-center">
|
||||
<button
|
||||
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 text-sm leading-tight rounded-md"
|
||||
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1rounded-md"
|
||||
@click="openGiftDialogToProject()"
|
||||
>
|
||||
Given To This...
|
||||
@@ -361,7 +361,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-bold mt-4">Given To This Project</h3>
|
||||
<h3 class="text-lg font-bold mt-4">Given To This Idea</h3>
|
||||
|
||||
<div v-if="givesToThis.length === 0" class="text-sm">
|
||||
(None yet. If you've seen something, say something by clicking a
|
||||
@@ -511,7 +511,7 @@
|
||||
<div v-if="activeDid && isRegistered">
|
||||
<div class="text-center">
|
||||
<button
|
||||
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 text-sm leading-tight rounded-md"
|
||||
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 rounded-md"
|
||||
@click="openGiftDialogFromProject()"
|
||||
>
|
||||
Given By This...
|
||||
|
||||
@@ -239,7 +239,7 @@
|
||||
class="border-b border-slate-300"
|
||||
>
|
||||
<a
|
||||
class="block py-4 flex gap-4 cursor-pointer"
|
||||
class="block py-4 flex gap-4"
|
||||
@click="onClickLoadProject(project.handleId)"
|
||||
>
|
||||
<div class="flex-none">
|
||||
@@ -279,8 +279,13 @@ import ProjectIcon from "../components/ProjectIcon.vue";
|
||||
import TopMessage from "../components/TopMessage.vue";
|
||||
import UserNameDialog from "../components/UserNameDialog.vue";
|
||||
import { Contact } from "../db/tables/contacts";
|
||||
import { didInfo, getHeaders, getPlanFromCache } from "../libs/endorserServer";
|
||||
import { OfferSummaryRecord, PlanData } from "../interfaces/records";
|
||||
import {
|
||||
didInfo,
|
||||
getHeaders,
|
||||
getPlanFromCache,
|
||||
OfferSummaryRecord,
|
||||
PlanData,
|
||||
} from "../libs/endorserServer";
|
||||
import * as libsUtil from "../libs/util";
|
||||
import { OnboardPage } from "../libs/util";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
@@ -109,11 +109,7 @@ test('Record something given', async ({ page }) => {
|
||||
|
||||
// Refresh home view and check gift
|
||||
await page.goto('./');
|
||||
await page.locator('li')
|
||||
.filter({ hasText: finalTitle })
|
||||
.locator('a.cursor-pointer')
|
||||
.filter({ hasText: finalTitle })
|
||||
.click();
|
||||
await page.locator('li').filter({ hasText: finalTitle }).locator('a').click();
|
||||
await expect(page.getByRole('heading', { name: 'Verifiable Claim Details' })).toBeVisible();
|
||||
await expect(page.getByText(finalTitle, { exact: true })).toBeVisible();
|
||||
const page1Promise = page.waitForEvent('popup');
|
||||
|
||||
@@ -88,8 +88,12 @@ import { test, expect } from '@playwright/test';
|
||||
import { importUser, createUniqueStringsArray, createRandomNumbersArray } from './testUtils';
|
||||
|
||||
test('Record 9 new gifts', async ({ page }) => {
|
||||
const giftCount = 9;
|
||||
const giftCount = 9; // because 10 has taken us above 30 seconds
|
||||
|
||||
// Standard text
|
||||
const standardTitle = 'Gift ';
|
||||
|
||||
// Field value arrays
|
||||
const finalTitles = [];
|
||||
const finalNumbers = [];
|
||||
|
||||
@@ -97,19 +101,21 @@ test('Record 9 new gifts', async ({ page }) => {
|
||||
const uniqueStrings = await createUniqueStringsArray(giftCount);
|
||||
const randomNumbers = await createRandomNumbersArray(giftCount);
|
||||
|
||||
// Populate arrays
|
||||
// Populate array with titles
|
||||
for (let i = 0; i < giftCount; i++) {
|
||||
finalTitles.push(standardTitle + uniqueStrings[i]);
|
||||
finalNumbers.push(randomNumbers[i]);
|
||||
let loopTitle = standardTitle + uniqueStrings[i];
|
||||
finalTitles.push(loopTitle);
|
||||
let loopNumber = randomNumbers[i];
|
||||
finalNumbers.push(loopNumber);
|
||||
}
|
||||
|
||||
// Import user 00
|
||||
await importUser(page, '00');
|
||||
|
||||
// Record new gifts with optimized waiting
|
||||
// Record new gifts
|
||||
for (let i = 0; i < giftCount; i++) {
|
||||
// Record gift
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
// Record something given
|
||||
await page.goto('./');
|
||||
if (i === 0) {
|
||||
await page.getByTestId('closeOnboardingAndFinish').click();
|
||||
}
|
||||
@@ -117,16 +123,11 @@ test('Record 9 new gifts', async ({ page }) => {
|
||||
await page.getByPlaceholder('What was given').fill(finalTitles[i]);
|
||||
await page.getByRole('spinbutton').fill(finalNumbers[i].toString());
|
||||
await page.getByRole('button', { name: 'Sign & Send' }).click();
|
||||
|
||||
// Wait for success and dismiss
|
||||
await expect(page.getByText('That gift was recorded.')).toBeVisible();
|
||||
await page.locator('div[role="alert"] button > svg.fa-xmark').click();
|
||||
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
|
||||
|
||||
// Verify gift in list with network idle wait
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
await expect(page.locator('ul#listLatestActivity li')
|
||||
.filter({ hasText: finalTitles[i] })
|
||||
.first())
|
||||
.toBeVisible({ timeout: 10000 });
|
||||
// Refresh home view and check gift
|
||||
await page.goto('./');
|
||||
await expect(page.locator('li').filter({ hasText: finalTitles[i] })).toBeVisible();
|
||||
}
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"failedTests": []
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"failedTests": [
|
||||
"a29eb57667e0fb28c7e9-7a80e551e7f16a766d0d",
|
||||
"a29eb57667e0fb28c7e9-1a8c76601bb6ea4f735c",
|
||||
"a29eb57667e0fb28c7e9-0a3670fa77fcd5ac9827",
|
||||
"a29eb57667e0fb28c7e9-90c8866cf70c7f96647d",
|
||||
"a29eb57667e0fb28c7e9-4abc584edcf7a6a12389",
|
||||
"a29eb57667e0fb28c7e9-3b443656a23fd8e7eb76",
|
||||
"a29eb57667e0fb28c7e9-1f63cf7a41b756ffe01f",
|
||||
"a29eb57667e0fb28c7e9-4eb03633761e58eac0a4",
|
||||
"db48a48c514e3e2940e5-cef25040a0b285eed2ba",
|
||||
"1c818805c9b0ac973736-726f18ba6163d57238c8",
|
||||
"c52ae54d86eda05904f3-adf7525a07e75f4e3cc2",
|
||||
"2fac21b9c9c3eb062631-9d2d2e9a199603c11b9b",
|
||||
"64242279fe0133650483-20fbacc4e45c5561df6c",
|
||||
"a7ff64a290be94f9d82c-e26ceb13031dafad1133",
|
||||
"868977083268005e6ec0-c27d226d34e20ba4863d",
|
||||
"5e149db5da4a5e319bcc-3298c84d0ebfff5e6d7c",
|
||||
"5e149db5da4a5e319bcc-1981ba81641b6000f80b",
|
||||
"2b5f6d3352de2040032d-bf5ed3a9483d90c396dd",
|
||||
"2b5f6d3352de2040032d-6f52c3699c55c19ccad8",
|
||||
"2b5f6d3352de2040032d-0f478a3208f64651daa1",
|
||||
"2b5f6d3352de2040032d-a05f542cad739ee3b5b9",
|
||||
"955bdfdfe05b442c0f5d-a9ec2b8bc7bd90ea0439",
|
||||
"955bdfdfe05b442c0f5d-2c38171f673436923a8b",
|
||||
"1a1fd29d3f0573e705e6-a3a6805908fe9a29ab11"
|
||||
]
|
||||
}
|
||||
@@ -8,4 +8,5 @@ web3>=6.0.0 # For Ethereum interaction
|
||||
eth-utils>=2.1.0 # For Ethereum utilities
|
||||
pyjwt>=2.8.0 # For JWT operations
|
||||
cryptography>=42.0.0 # For key format conversion
|
||||
jwcrypto
|
||||
jwcrypto
|
||||
setuptools
|
||||
|
||||
@@ -16,11 +16,14 @@ export async function createBuildConfig(mode: string) {
|
||||
const isElectron = mode === "electron";
|
||||
const isCapacitor = mode === "capacitor";
|
||||
const isPyWebView = mode === "pywebview";
|
||||
const isWeb = mode === "web";
|
||||
|
||||
// Explicitly set platform
|
||||
process.env.VITE_PLATFORM = mode;
|
||||
|
||||
if (isElectron || isPyWebView || isCapacitor) {
|
||||
if (isWeb) {
|
||||
process.env.VITE_PWA_ENABLED = 'true';
|
||||
} else {
|
||||
process.env.VITE_PWA_ENABLED = 'false';
|
||||
}
|
||||
|
||||
@@ -42,7 +45,7 @@ export async function createBuildConfig(mode: string) {
|
||||
define: {
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||
'process.env.VITE_PLATFORM': JSON.stringify(mode),
|
||||
'process.env.VITE_PWA_ENABLED': JSON.stringify(!(isElectron || isPyWebView || isCapacitor)),
|
||||
'process.env.VITE_PWA_ENABLED': isWeb ? "true" : "false",
|
||||
__dirname: isElectron ? JSON.stringify(process.cwd()) : '""',
|
||||
},
|
||||
resolve: {
|
||||
|
||||
@@ -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: './',
|
||||
});
|
||||
});
|
||||