diff --git a/android/.gitignore b/android/.gitignore deleted file mode 100644 index 48354a3..0000000 --- a/android/.gitignore +++ /dev/null @@ -1,101 +0,0 @@ -# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore - -# Built application files -*.apk -*.aar -*.ap_ -*.aab - -# Files for the ART/Dalvik VM -*.dex - -# Java class files -*.class - -# Generated files -bin/ -gen/ -out/ -# Uncomment the following line in case you need and you don't have the release build type files in your app -# release/ - -# Gradle files -.gradle/ -build/ - -# Local configuration file (sdk path, etc) -local.properties - -# Proguard folder generated by Eclipse -proguard/ - -# Log Files -*.log - -# Android Studio Navigation editor temp files -.navigation/ - -# Android Studio captures folder -captures/ - -# IntelliJ -*.iml -.idea/workspace.xml -.idea/tasks.xml -.idea/gradle.xml -.idea/assetWizardSettings.xml -.idea/dictionaries -.idea/libraries -# Android Studio 3 in .gitignore file. -.idea/caches -.idea/modules.xml -# Comment next line if keeping position of elements in Navigation Editor is relevant for you -.idea/navEditor.xml - -# Keystore files -# Uncomment the following lines if you do not want to check your keystore files in. -#*.jks -#*.keystore - -# External native build folder generated in Android Studio 2.2 and later -.externalNativeBuild -.cxx/ - -# Google Services (e.g. APIs or Firebase) -# google-services.json - -# Freeline -freeline.py -freeline/ -freeline_project_description.json - -# fastlane -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots -fastlane/test_output -fastlane/readme.md - -# Version control -vcs.xml - -# lint -lint/intermediates/ -lint/generated/ -lint/outputs/ -lint/tmp/ -# lint/reports/ - -# Android Profiling -*.hprof - -# Cordova plugins for Capacitor -capacitor-cordova-android-plugins - -# Copied web assets -app/src/main/assets/public - -# Generated Config files -app/src/main/assets/capacitor.config.json -app/src/main/assets/capacitor.plugins.json -app/src/main/res/xml/config.xml diff --git a/android/app/.gitignore b/android/app/.gitignore deleted file mode 100644 index 043df80..0000000 --- a/android/app/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/build/* -!/build/.npmkeep diff --git a/android/app/build.gradle b/android/app/build.gradle deleted file mode 100644 index 3f5e684..0000000 --- a/android/app/build.gradle +++ /dev/null @@ -1,54 +0,0 @@ -apply plugin: 'com.android.application' - -android { - namespace "com.example.app" - compileSdk rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "com.example.app" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - 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. - // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61 - ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' - } - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } -} - -repositories { - flatDir{ - dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' - } -} - -dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" - implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion" - implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion" - implementation project(':capacitor-android') - testImplementation "junit:junit:$junitVersion" - androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" - androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" - implementation project(':capacitor-cordova-android-plugins') -} - -apply from: 'capacitor.build.gradle' - -try { - def servicesJSON = file('google-services.json') - if (servicesJSON.text) { - apply plugin: 'com.google.gms.google-services' - } -} catch(Exception e) { - logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work") -} diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle deleted file mode 100644 index d4f22b6..0000000 --- a/android/app/capacitor.build.gradle +++ /dev/null @@ -1,23 +0,0 @@ -// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN - -android { - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } -} - -apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" -dependencies { - implementation project(':capacitor-browser') - implementation project(':capacitor-filesystem') - implementation project(':capacitor-local-notifications') - implementation project(':evva-capacitor-secure-storage-plugin') - implementation project(':transistorsoft-capacitor-background-fetch') - implementation "androidx.webkit:webkit:1.4.0" -} - - -if (hasProperty('postBuildExtras')) { - postBuildExtras() -} diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro deleted file mode 100644 index f1b4245..0000000 --- a/android/app/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java b/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java deleted file mode 100644 index f2c2217..0000000 --- a/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.getcapacitor.myapp; - -import static org.junit.Assert.*; - -import android.content.Context; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - - @Test - public void useAppContext() throws Exception { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - - assertEquals("com.getcapacitor.app", appContext.getPackageName()); - } -} diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 2b2013c..0000000 --- a/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/app/src/main/java/com/example/app/BackgroundFetchHeadlessTask.java b/android/app/src/main/java/com/example/app/BackgroundFetchHeadlessTask.java deleted file mode 100644 index fac2dc1..0000000 --- a/android/app/src/main/java/com/example/app/BackgroundFetchHeadlessTask.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.example.app; - - -import android.content.Context; -import android.util.Log; - -import com.transistorsoft.tsbackgroundfetch.BackgroundFetch; -import com.transistorsoft.tsbackgroundfetch.BGTask; - -public class BackgroundFetchHeadlessTask{ - public void onFetch(Context context, BGTask task) { - // Get a reference to the BackgroundFetch Android API. - BackgroundFetch backgroundFetch = BackgroundFetch.getInstance(context); - // Get the taskId. - String taskId = task.getTaskId(); - // Log a message to adb logcat. - Log.d("MyHeadlessTask", "BackgroundFetchHeadlessTask onFetch -- CUSTOM IMPLEMENTATION: " + taskId); - - boolean isTimeout = task.getTimedOut(); - // Is this a timeout? - if (isTimeout) { - backgroundFetch.finish(taskId); - return; - } - // Do your work here... - // - // - // Signal finish just like the Javascript API. - backgroundFetch.finish(taskId); - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/example/app/MainActivity.java b/android/app/src/main/java/com/example/app/MainActivity.java deleted file mode 100644 index 966f32d..0000000 --- a/android/app/src/main/java/com/example/app/MainActivity.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.example.app; - -import com.getcapacitor.BridgeActivity; - -public class MainActivity extends BridgeActivity {} diff --git a/android/app/src/main/res/drawable-land-hdpi/splash.png b/android/app/src/main/res/drawable-land-hdpi/splash.png deleted file mode 100644 index e31573b..0000000 Binary files a/android/app/src/main/res/drawable-land-hdpi/splash.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-land-mdpi/splash.png b/android/app/src/main/res/drawable-land-mdpi/splash.png deleted file mode 100644 index f7a6492..0000000 Binary files a/android/app/src/main/res/drawable-land-mdpi/splash.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-land-xhdpi/splash.png b/android/app/src/main/res/drawable-land-xhdpi/splash.png deleted file mode 100644 index 8077255..0000000 Binary files a/android/app/src/main/res/drawable-land-xhdpi/splash.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-land-xxhdpi/splash.png b/android/app/src/main/res/drawable-land-xxhdpi/splash.png deleted file mode 100644 index 14c6c8f..0000000 Binary files a/android/app/src/main/res/drawable-land-xxhdpi/splash.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-land-xxxhdpi/splash.png b/android/app/src/main/res/drawable-land-xxxhdpi/splash.png deleted file mode 100644 index 244ca25..0000000 Binary files a/android/app/src/main/res/drawable-land-xxxhdpi/splash.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-port-hdpi/splash.png b/android/app/src/main/res/drawable-port-hdpi/splash.png deleted file mode 100644 index 74faaa5..0000000 Binary files a/android/app/src/main/res/drawable-port-hdpi/splash.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-port-mdpi/splash.png b/android/app/src/main/res/drawable-port-mdpi/splash.png deleted file mode 100644 index e944f4a..0000000 Binary files a/android/app/src/main/res/drawable-port-mdpi/splash.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-port-xhdpi/splash.png b/android/app/src/main/res/drawable-port-xhdpi/splash.png deleted file mode 100644 index 564a82f..0000000 Binary files a/android/app/src/main/res/drawable-port-xhdpi/splash.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-port-xxhdpi/splash.png b/android/app/src/main/res/drawable-port-xxhdpi/splash.png deleted file mode 100644 index bfabe68..0000000 Binary files a/android/app/src/main/res/drawable-port-xxhdpi/splash.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-port-xxxhdpi/splash.png b/android/app/src/main/res/drawable-port-xxxhdpi/splash.png deleted file mode 100644 index 6929071..0000000 Binary files a/android/app/src/main/res/drawable-port-xxxhdpi/splash.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index c7bd21d..0000000 --- a/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - diff --git a/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index d5fccc5..0000000 --- a/android/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/app/src/main/res/drawable/qort.png b/android/app/src/main/res/drawable/qort.png deleted file mode 100644 index 39d090f..0000000 Binary files a/android/app/src/main/res/drawable/qort.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/splash.png b/android/app/src/main/res/drawable/splash.png deleted file mode 100644 index a760af2..0000000 Binary files a/android/app/src/main/res/drawable/splash.png and /dev/null differ diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index b5ad138..0000000 --- a/android/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 90f9580..0000000 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 12a409b..0000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_adaptive_back.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_adaptive_back.png deleted file mode 100644 index a05f3fc..0000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_adaptive_back.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_adaptive_fore.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_adaptive_fore.png deleted file mode 100644 index d6bdb01..0000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_adaptive_fore.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3966b05..0000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_adaptive_back.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_adaptive_back.png deleted file mode 100644 index 3814b23..0000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_adaptive_back.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_adaptive_fore.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_adaptive_fore.png deleted file mode 100644 index 1907b24..0000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_adaptive_fore.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index ff8e391..0000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_adaptive_back.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_adaptive_back.png deleted file mode 100644 index 2e1846f..0000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_adaptive_back.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_adaptive_fore.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_adaptive_fore.png deleted file mode 100644 index 123ff93..0000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_adaptive_fore.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index f1f3036..0000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_adaptive_back.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_adaptive_back.png deleted file mode 100644 index fe154c8..0000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_adaptive_back.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_adaptive_fore.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_adaptive_fore.png deleted file mode 100644 index c4fdc57..0000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_adaptive_fore.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 7191d0c..0000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_adaptive_back.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_adaptive_back.png deleted file mode 100644 index b640524..0000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_adaptive_back.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png deleted file mode 100644 index 69a0eb8..0000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png and /dev/null differ diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml deleted file mode 100644 index c5d5899..0000000 --- a/android/app/src/main/res/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #FFFFFF - \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml deleted file mode 100644 index 624265c..0000000 --- a/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - Qortal - Qortal - com.example.app - com.example.app - diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml deleted file mode 100644 index be874e5..0000000 --- a/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/xml/file_paths.xml b/android/app/src/main/res/xml/file_paths.xml deleted file mode 100644 index bd0c4d8..0000000 --- a/android/app/src/main/res/xml/file_paths.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java b/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java deleted file mode 100644 index 0297327..0000000 --- a/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.getcapacitor.myapp; - -import static org.junit.Assert.*; - -import org.junit.Test; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index 7e1c3f3..0000000 --- a/android/build.gradle +++ /dev/null @@ -1,33 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:8.2.1' - classpath 'com.google.gms:google-services:4.4.0' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -apply from: "variables.gradle" - -allprojects { - repositories { - google() - mavenCentral() - maven { - // capacitor-background-fetch - url("${project(':transistorsoft-capacitor-background-fetch').projectDir}/libs") - } - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle deleted file mode 100644 index c72458d..0000000 --- a/android/capacitor.settings.gradle +++ /dev/null @@ -1,18 +0,0 @@ -// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN -include ':capacitor-android' -project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') - -include ':capacitor-browser' -project(':capacitor-browser').projectDir = new File('../node_modules/@capacitor/browser/android') - -include ':capacitor-filesystem' -project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android') - -include ':capacitor-local-notifications' -project(':capacitor-local-notifications').projectDir = new File('../node_modules/@capacitor/local-notifications/android') - -include ':evva-capacitor-secure-storage-plugin' -project(':evva-capacitor-secure-storage-plugin').projectDir = new File('../node_modules/@evva/capacitor-secure-storage-plugin/android') - -include ':transistorsoft-capacitor-background-fetch' -project(':transistorsoft-capacitor-background-fetch').projectDir = new File('../node_modules/@transistorsoft/capacitor-background-fetch/android') diff --git a/android/gradle.properties b/android/gradle.properties deleted file mode 100644 index 2e87c52..0000000 --- a/android/gradle.properties +++ /dev/null @@ -1,22 +0,0 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true - -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 033e24c..0000000 Binary files a/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index c747538..0000000 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew deleted file mode 100755 index fcb6fca..0000000 --- a/android/gradlew +++ /dev/null @@ -1,248 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat deleted file mode 100644 index 6689b85..0000000 --- a/android/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/android/settings.gradle b/android/settings.gradle deleted file mode 100644 index 3b4431d..0000000 --- a/android/settings.gradle +++ /dev/null @@ -1,5 +0,0 @@ -include ':app' -include ':capacitor-cordova-android-plugins' -project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') - -apply from: 'capacitor.settings.gradle' \ No newline at end of file diff --git a/android/variables.gradle b/android/variables.gradle deleted file mode 100644 index 8ef305d..0000000 --- a/android/variables.gradle +++ /dev/null @@ -1,16 +0,0 @@ -ext { - minSdkVersion = 22 - compileSdkVersion = 34 - targetSdkVersion = 34 - androidxActivityVersion = '1.8.0' - androidxAppCompatVersion = '1.6.1' - androidxCoordinatorLayoutVersion = '1.2.0' - androidxCoreVersion = '1.12.0' - androidxFragmentVersion = '1.6.2' - coreSplashScreenVersion = '1.0.1' - androidxWebkitVersion = '1.9.0' - junitVersion = '4.13.2' - androidxJunitVersion = '1.1.5' - androidxEspressoCoreVersion = '3.5.1' - cordovaAndroidVersion = '10.1.1' -} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 9bb5342..7d3a7c7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,13 +29,12 @@ import { decryptStoredWallet } from './utils/decryptWallet'; import { CountdownCircleTimer } from 'react-countdown-circle-timer'; import Logo1Dark from './assets/svgs/Logo1Dark.svg'; import RefreshIcon from '@mui/icons-material/Refresh'; +import DownloadIcon from '@mui/icons-material/Download'; import Copy from './assets/svgs/Copy.svg'; import ltcLogo from './assets/ltc.png'; import PersonSearchIcon from '@mui/icons-material/PersonSearch'; import qortLogo from './assets/qort.png'; import { CopyToClipboard } from 'react-copy-to-clipboard'; -import { Download } from './assets/Icons/Download.tsx'; -import { Logout } from './assets/Icons/Logout.tsx'; import { Return } from './assets/Icons/Return.tsx'; import WarningIcon from '@mui/icons-material/Warning'; import Success from './assets/svgs/Success.svg'; @@ -73,6 +72,7 @@ import { TaskManager } from './components/TaskManager/TaskManager.tsx'; import { useModal } from './common/useModal'; import { CustomizedSnackbars } from './components/Snackbar/Snackbar'; import SettingsIcon from '@mui/icons-material/Settings'; +import LogoutIcon from '@mui/icons-material/Logout'; import HelpIcon from '@mui/icons-material/Help'; import { cleanUrl, @@ -118,9 +118,7 @@ import { import { useAppFullScreen } from './useAppFullscreen'; import { NotAuthenticated } from './ExtStates/NotAuthenticated'; import { handleGetFileFromIndexedDB } from './utils/indexedDB'; -import { CoreSyncStatus } from './components/CoreSyncStatus'; import { Wallets } from './Wallets'; -import { RandomSentenceGenerator } from './utils/seedPhrase/RandomSentenceGenerator'; import { useFetchResources } from './common/useFetchResources'; import { Tutorials } from './components/Tutorials/Tutorials'; import { useHandleTutorials } from './components/Tutorials/useHandleTutorials'; @@ -182,28 +180,6 @@ const defaultValues: MyContextInterface = { message: '', }, }; -export let isMobile = false; - -const isMobileDevice = () => { - const userAgent = navigator.userAgent || navigator.vendor || window.opera; - - if (/android/i.test(userAgent)) { - return true; // Android device - } - - if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) { - return true; // iOS device - } - - return false; -}; - -if (isMobileDevice()) { - isMobile = true; - console.log('Running on a mobile device'); -} else { - console.log('Running on a desktop'); -} export const allQueues = { requestQueueCommentCount: requestQueueCommentCount, @@ -313,7 +289,6 @@ export const isMainWindow = true; function App() { const [extState, setExtstate] = useState('not-authenticated'); const [desktopViewMode, setDesktopViewMode] = useState('home'); - const [backupjson, setBackupjson] = useState(null); const [rawWallet, setRawWallet] = useState(null); const [ltcBalanceLoading, setLtcBalanceLoading] = useState(false); @@ -436,16 +411,20 @@ function App() { const [isOpenMinting, setIsOpenMinting] = useState(false); const { toggleFullScreen } = useAppFullScreen(setFullScreen); const generatorRef = useRef(null); + const exportSeedphrase = () => { const seedPhrase = generatorRef.current.parsedString; saveSeedPhraseToDisk(seedPhrase); }; + const passwordRef = useRef(null); + useEffect(() => { if (extState === 'wallet-dropped' && passwordRef.current) { passwordRef.current.focus(); } }, [extState]); + useEffect(() => { const isDevModeFromStorage = localStorage.getItem('isEnabledDevMode'); if (isDevModeFromStorage) { @@ -491,31 +470,38 @@ function App() { ); }; }, [toggleFullScreen]); + //resets for recoil const resetAtomSortablePinnedAppsAtom = useResetRecoilState( sortablePinnedAppsAtom ); + const resetAtomIsUsingImportExportSettingsAtom = useResetRecoilState( isUsingImportExportSettingsAtom ); const resetAtomCanSaveSettingToQdnAtom = useResetRecoilState( canSaveSettingToQdnAtom ); + const resetAtomSettingsQDNLastUpdatedAtom = useResetRecoilState( settingsQDNLastUpdatedAtom ); + const resetAtomSettingsLocalLastUpdatedAtom = useResetRecoilState( settingsLocalLastUpdatedAtom ); + const resetAtomOldPinnedAppsAtom = useResetRecoilState(oldPinnedAppsAtom); const resetAtomQMailLastEnteredTimestampAtom = useResetRecoilState( qMailLastEnteredTimestampAtom ); + const resetAtomMailsAtom = useResetRecoilState(mailsAtom); const resetGroupPropertiesAtom = useResetRecoilState(groupsPropertiesAtom); const resetLastPaymentSeenTimestampAtom = useResetRecoilState( lastPaymentSeenTimestampAtom ); + const resetAllRecoil = () => { resetAtomSortablePinnedAppsAtom(); resetAtomCanSaveSettingToQdnAtom(); @@ -528,34 +514,11 @@ function App() { resetGroupPropertiesAtom(); resetLastPaymentSeenTimestampAtom(); }; - useEffect(() => { - if (!isMobile) return; - // Function to set the height of the app to the viewport height - const resetHeight = () => { - const height = window.visualViewport - ? window.visualViewport.height - : window.innerHeight; - // Set the height to the root element (usually #root) - document.getElementById('root').style.height = height + 'px'; - setRootHeight(height + 'px'); - }; - // Set the initial height - resetHeight(); - - // Add event listeners for resize and visualViewport changes - window.addEventListener('resize', resetHeight); - window.visualViewport?.addEventListener('resize', resetHeight); - - // Clean up the event listeners when the component unmounts - return () => { - window.removeEventListener('resize', resetHeight); - window.visualViewport?.removeEventListener('resize', resetHeight); - }; - }, []); const handleSetGlobalApikey = (key) => { globalApiKey = key; }; + useEffect(() => { try { setIsLoading(true); @@ -1232,14 +1195,6 @@ function App() { // Handler for when the window gains focus const handleFocus = () => { setIsFocused(true); - if (isMobile) { - window.sendMessage('clearAllNotifications', {}).catch((error) => { - console.error( - 'Failed to clear notifications:', - error.message || 'An error occurred' - ); - }); - } }; // Handler for when the window loses focus @@ -1255,14 +1210,6 @@ function App() { const handleVisibilityChange = () => { if (document.visibilityState === 'visible') { setIsFocused(true); - if (isMobile) { - window.sendMessage('clearAllNotifications', {}).catch((error) => { - console.error( - 'Failed to clear notifications:', - error.message || 'An error occurred' - ); - }); - } } else { setIsFocused(false); } @@ -1315,7 +1262,7 @@ function App() { return ( - {isMobile && ( - - { - setIsOpenDrawerProfile(false); - }} - sx={{ - cursor: 'pointer', - }} - /> - - )} {desktopViewMode !== 'apps' && desktopViewMode !== 'dev' && desktopViewMode !== 'chat' && <>{renderProfileLeft()}} @@ -1609,51 +1538,43 @@ function App() { > - {!isMobile && ( - <> - - - LOG OUT - - } - placement="left" - arrow - sx={{ fontSize: '24' }} - slotProps={{ - tooltip: { - sx: { - color: theme.palette.text.primary, - backgroundColor: theme.palette.background.default, - }, - }, - arrow: { - sx: { - color: theme.palette.text.primary, - }, - }, - }} - > - { + logoutFunc(); + setIsOpenDrawerProfile(false); + }} + > + { - logoutFunc(); - setIsOpenDrawerProfile(false); - }} - /> - - - )} + > + LOGOUT + + } + placement="left" + arrow + sx={{ fontSize: '24' }} + slotProps={{ + tooltip: { + sx: { + color: theme.palette.text.primary, + backgroundColor: theme.palette.background.default, + }, + }, + arrow: { + sx: { + color: theme.palette.text.primary, + }, + }, + }} + > + + + @@ -1964,41 +1885,39 @@ function App() { )} - - BACKUP WALLET - - } - placement="left" - arrow - sx={{ fontSize: '24' }} - slotProps={{ - tooltip: { - sx: { - color: theme.palette.text.primary, - backgroundColor: theme.palette.background.default, - }, - }, - arrow: { - sx: { - color: theme.palette.text.primary, - }, - }, + + { + setExtstate('download-wallet'); + setIsOpenDrawerProfile(false); }} > - + BACKUP WALLET + + } + placement="left" + arrow + sx={{ fontSize: '24' }} + slotProps={{ + tooltip: { + sx: { + color: theme.palette.text.primary, + backgroundColor: theme.palette.background.default, + }, + }, + arrow: { + sx: { + color: theme.palette.text.primary, + }, + }, }} - onClick={() => { - setExtstate('download-wallet'); - setIsOpenDrawerProfile(false); - }} - /> - + > + + + @@ -2009,7 +1928,7 @@ function App() { return ( - {!isMobile && renderProfile()} + {renderProfile()} )} {isOpenSendQort && isMainWindow && ( + + = ({ - color, - opacity, - ...children -}) => { - const theme = useTheme(); - - const setColor = color ? color : theme.palette.text.primary; - const setOpacity = opacity ? opacity : 1; - - return ( - - - - ); -}; diff --git a/src/assets/Icons/Logout.tsx b/src/assets/Icons/Logout.tsx deleted file mode 100644 index f7d9c57..0000000 --- a/src/assets/Icons/Logout.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useTheme } from '@mui/material'; -import { SVGProps } from './interfaces'; - -export const Logout: React.FC = ({ color, opacity, ...children }) => { - const theme = useTheme(); - - const setColor = color ? color : theme.palette.text.primary; - const setOpacity = opacity ? opacity : 1; - - return ( - - - - ); -}; diff --git a/src/assets/Icons/LogoutIcon.tsx b/src/assets/Icons/LogoutIcon.tsx deleted file mode 100644 index d1a4408..0000000 --- a/src/assets/Icons/LogoutIcon.tsx +++ /dev/null @@ -1,18 +0,0 @@ -export const LogoutIcon = ({ color, height = 20, width = 18 }) => { - return ( - - - - ); -}; diff --git a/src/assets/Icons/NavAdd.tsx b/src/assets/Icons/NavAdd.tsx index dd45bad..c234be2 100644 --- a/src/assets/Icons/NavAdd.tsx +++ b/src/assets/Icons/NavAdd.tsx @@ -25,7 +25,7 @@ export const NavAdd: React.FC = ({ color, opacity, ...children }) => { ); diff --git a/src/assets/Icons/NavCloseTab.tsx b/src/assets/Icons/NavCloseTab.tsx index 8b2734e..6062175 100644 --- a/src/assets/Icons/NavCloseTab.tsx +++ b/src/assets/Icons/NavCloseTab.tsx @@ -32,14 +32,14 @@ export const NavCloseTab: React.FC = ({ stroke={theme.palette.text.primary} stroke-width="2" fill={setColor} - fill-opacity={setOpacity} + fillOpacity={setOpacity} /> ); diff --git a/src/assets/Icons/NavMoreMenu.tsx b/src/assets/Icons/NavMoreMenu.tsx index ca641ba..2916f49 100644 --- a/src/assets/Icons/NavMoreMenu.tsx +++ b/src/assets/Icons/NavMoreMenu.tsx @@ -23,7 +23,7 @@ export const NavMoreMenu: React.FC = ({ ); diff --git a/src/assets/Icons/QappDevelopText.tsx b/src/assets/Icons/QappDevelopText.tsx new file mode 100644 index 0000000..e0856cf --- /dev/null +++ b/src/assets/Icons/QappDevelopText.tsx @@ -0,0 +1,30 @@ +import { useTheme } from '@mui/material'; +import { SVGProps } from './interfaces'; + +export const QappDevelopText: React.FC = ({ + color, + opacity, + ...children +}) => { + const theme = useTheme(); + + const setColor = color ? color : theme.palette.text.primary; + const setOpacity = opacity ? opacity : 1; + + return ( + + + + ); +}; diff --git a/src/assets/Icons/QappLibraryText.tsx b/src/assets/Icons/QappLibraryText.tsx new file mode 100644 index 0000000..146717e --- /dev/null +++ b/src/assets/Icons/QappLibraryText.tsx @@ -0,0 +1,34 @@ +import { useTheme } from '@mui/material'; +import { SVGProps } from './interfaces'; + +export const QappLibraryText: React.FC = ({ + color, + opacity, + ...children +}) => { + const theme = useTheme(); + + const setColor = color ? color : theme.palette.text.primary; + const setOpacity = opacity ? opacity : 1; + + return ( + + + + + ); +}; diff --git a/src/assets/Icons/Return.tsx b/src/assets/Icons/Return.tsx index df0abad..81a6690 100644 --- a/src/assets/Icons/Return.tsx +++ b/src/assets/Icons/Return.tsx @@ -20,7 +20,7 @@ export const Return: React.FC = ({ color, opacity, ...children }) => { clipRule="evenodd" d="M2.645 5.81803H15C15.9471 5.81803 16.8557 6.20131 17.5257 6.88278C18.195 7.56497 18.5714 8.49007 18.5714 9.45445V10.909C18.5714 11.8734 18.195 12.7985 17.5257 13.4807C16.8557 14.1622 15.9471 14.5454 15 14.5454C12.0164 14.5454 8.57143 14.5454 8.57143 14.5454C8.17714 14.5454 7.85714 14.8713 7.85714 15.2727C7.85714 15.6742 8.17714 16 8.57143 16H15C16.3264 16 17.5979 15.464 18.5357 14.5091C19.4736 13.5541 20 12.2596 20 10.909C20 10.4268 20 9.93664 20 9.45445C20 8.10461 19.4736 6.80932 18.5357 5.8544C17.5979 4.9002 16.3264 4.36347 15 4.36347H2.645L6.17929 1.27906C6.47857 1.01797 6.51286 0.55832 6.25643 0.253588C6 -0.0511433 5.54857 -0.0860541 5.24929 0.175041L0.249285 4.53874C0.0914279 4.67692 0 4.87838 0 5.09075C0 5.30312 0.0914279 5.50458 0.249285 5.64276L5.24929 10.0065C5.54857 10.2676 6 10.2326 6.25643 9.92791C6.51286 9.62318 6.47857 9.16353 6.17929 8.90244L2.645 5.81803Z" fill={setColor} - fill-opacity={opacity} + fillOpacity={opacity} /> ); diff --git a/src/assets/Icons/Search.tsx b/src/assets/Icons/Search.tsx new file mode 100644 index 0000000..64ae644 --- /dev/null +++ b/src/assets/Icons/Search.tsx @@ -0,0 +1,26 @@ +import { useTheme } from '@mui/material'; +import { SVGProps } from './interfaces'; + +export const Search: React.FC = ({ color, opacity, ...children }) => { + const theme = useTheme(); + + const setColor = color ? color : theme.palette.text.primary; + const setOpacity = opacity ? opacity : 1; + + return ( + + + + ); +}; diff --git a/src/assets/react.svg b/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/svgs/qappDevelopText.svg b/src/assets/svgs/qappDevelopText.svg deleted file mode 100644 index 3aa786a..0000000 --- a/src/assets/svgs/qappDevelopText.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/svgs/qappLibraryText.svg b/src/assets/svgs/qappLibraryText.svg deleted file mode 100644 index 297c466..0000000 --- a/src/assets/svgs/qappLibraryText.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/background.ts b/src/background.ts index dad784a..3811a42 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,5 +1,4 @@ // @ts-nocheck - import './qortalRequests'; import { isArray } from 'lodash'; import { @@ -30,7 +29,6 @@ import { RequestQueueWithPromise } from './utils/queue/queue'; import { validateAddress } from './utils/validateAddress'; import { Sha256 } from 'asmcrypto.js'; import { TradeBotRespondMultipleRequest } from './transactions/TradeBotRespondMultipleRequest'; - import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from './constants/resourceTypes'; import { addDataPublishesCase, @@ -111,6 +109,7 @@ export let groupSecretkeys = {}; export function cleanUrl(url) { return url?.replace(/^(https?:\/\/)?(www\.)?/, ''); } + export function getProtocol(url) { if (url?.startsWith('https://')) { return 'https'; @@ -130,7 +129,6 @@ export const groupApiLocal = 'http://127.0.0.1:12391'; export const groupApiSocketLocal = 'ws://127.0.0.1:12391'; const timeDifferenceForNotificationChatsBackground = 86400000; const requestQueueAnnouncements = new RequestQueueWithPromise(1); -let isMobile = true; function handleNotificationClick(notificationId) { // Decode the notificationId if it was encoded @@ -193,26 +191,6 @@ function handleNotificationClick(notificationId) { } } -const isMobileDevice = () => { - const userAgent = navigator.userAgent || navigator.vendor || window.opera; - - if (/android/i.test(userAgent)) { - return true; // Android device - } - - if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) { - return true; // iOS device - } - - return false; -}; - -if (isMobileDevice()) { - isMobile = true; - console.log('Running on a mobile device'); -} else { - console.log('Running on a desktop'); -} const allQueues = { requestQueueAnnouncements: requestQueueAnnouncements, }; @@ -264,7 +242,9 @@ export const getForeignKey = async (foreignBlockchain) => { }; export const pauseAllQueues = () => controlAllQueues('pause'); + export const resumeAllQueues = () => controlAllQueues('resume'); + export const checkDifference = ( createdTimestamp, diff = timeDifferenceForNotificationChatsBackground @@ -302,6 +282,7 @@ export const getBaseApi = async (customApi?: string) => { return groupApi; } }; + export const isUsingLocal = async () => { const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously if (apiKey?.url) { @@ -345,13 +326,13 @@ const proxyAccountAddress = 'QXPejUe5Za1KD3zCMViWCX35AreMQ9H7ku'; const proxyAccountPublicKey = '5hP6stDWybojoDw5t8z9D51nV945oMPX7qBd29rhX1G7'; const pendingResponses = new Map(); let groups = null; - let socket; let timeoutId; let groupSocketTimeout; let socketTimeout: any; let interval; let intervalThreads; + // Function to check each API endpoint export async function findUsableApi() { for (const endpoint of apiEndpoints) { @@ -435,6 +416,7 @@ async function checkWebviewFocus() { window.addEventListener('message', handleMessage); }); } + const worker = new ChatComputePowWorker(); export async function performPowTask(chatBytes, difficulty) { @@ -584,6 +566,7 @@ const handleNotificationDirect = async (directs) => { setChatHeadsDirect(dataDirects); } }; + async function getThreadActivity(): Promise { const wallet = await getSaveWallet(); const address = wallet.address0; @@ -852,6 +835,7 @@ export async function getNameInfoForOthers(address) { return ''; } } + export async function getAddressInfo(address) { const validApi = await getBaseApi(); const response = await fetch(validApi + '/addresses/' + address); @@ -932,6 +916,7 @@ async function getTradeInfo(qortalAtAddress) { const data = await response.json(); return data; } + async function getTradesInfo(qortalAtAddresses) { // Use Promise.all to fetch data for all addresses concurrently const trades = await Promise.all( @@ -951,6 +936,7 @@ export async function getBalanceInfo() { const data = await response.json(); return data; } + export async function getLTCBalance() { const wallet = await getSaveWallet(); let _url = `${buyTradeNodeBaseUrl}/crosschain/ltc/walletbalance`; @@ -1075,6 +1061,7 @@ const transaction = async ( data: res, }; }; + const makeTransactionRequest = async ( receiver, lastRef, @@ -1113,6 +1100,7 @@ export const getLastRef = async () => { const data = await response.text(); return data; }; + export const sendQortFee = async (): Promise => { const validApi = await getBaseApi(); const response = await fetch( @@ -1386,6 +1374,7 @@ export async function signChatFunc( } return response; } + function sbrk(size, heap) { let brk = 512 * 1024; // stack top let old = brk; diff --git a/src/backgroundFunctions/encryption.ts b/src/backgroundFunctions/encryption.ts index 8c7ec8b..6cc0150 100644 --- a/src/backgroundFunctions/encryption.ts +++ b/src/backgroundFunctions/encryption.ts @@ -1,327 +1,368 @@ -import { getBaseApi } from "../background"; -import { createSymmetricKeyAndNonce, decryptGroupData, encryptDataGroup, objectToBase64 } from "../qdn/encryption/group-encryption"; -import { publishData } from "../qdn/publish/pubish"; -import { getData } from "../utils/chromeStorage"; -import { RequestQueueWithPromise } from "../utils/queue/queue"; - +import { getBaseApi } from '../background'; +import { + createSymmetricKeyAndNonce, + decryptGroupData, + encryptDataGroup, + objectToBase64, +} from '../qdn/encryption/group-encryption'; +import { publishData } from '../qdn/publish/pubish'; +import { getData } from '../utils/chromeStorage'; +import { RequestQueueWithPromise } from '../utils/queue/queue'; export const requestQueueGetPublicKeys = new RequestQueueWithPromise(10); const apiEndpoints = [ - "https://api.qortal.org", - "https://api2.qortal.org", - "https://appnode.qortal.org", - "https://apinode.qortalnodes.live", - "https://apinode1.qortalnodes.live", - "https://apinode2.qortalnodes.live", - "https://apinode3.qortalnodes.live", - "https://apinode4.qortalnodes.live", + 'https://api.qortal.org', + 'https://api2.qortal.org', + 'https://appnode.qortal.org', + 'https://apinode.qortalnodes.live', + 'https://apinode1.qortalnodes.live', + 'https://apinode2.qortalnodes.live', + 'https://apinode3.qortalnodes.live', + 'https://apinode4.qortalnodes.live', ]; async function findUsableApi() { - for (const endpoint of apiEndpoints) { - try { - const response = await fetch(`${endpoint}/admin/status`); - if (!response.ok) throw new Error("Failed to fetch"); - - const data = await response.json(); - if (data.isSynchronizing === false && data.syncPercent === 100) { - console.log(`Usable API found: ${endpoint}`); - return endpoint; - } else { - console.log(`API not ready: ${endpoint}`); - } - } catch (error) { - console.error(`Error checking API ${endpoint}:`, error); + for (const endpoint of apiEndpoints) { + try { + const response = await fetch(`${endpoint}/admin/status`); + if (!response.ok) throw new Error('Failed to fetch'); + + const data = await response.json(); + if (data.isSynchronizing === false && data.syncPercent === 100) { + console.log(`Usable API found: ${endpoint}`); + return endpoint; + } else { + console.log(`API not ready: ${endpoint}`); } + } catch (error) { + console.error(`Error checking API ${endpoint}:`, error); } - - throw new Error("No usable API found"); } + throw new Error('No usable API found'); +} async function getSaveWallet() { - const res = await getData("walletInfo").catch(() => null); + const res = await getData('walletInfo').catch(() => null); - if (res) { - return res - } else { - throw new Error("No wallet saved"); - } - } -export async function getNameInfo() { - const wallet = await getSaveWallet(); - const address = wallet.address0; - const validApi = await getBaseApi() - const response = await fetch(validApi + "/names/address/" + address); - const nameData = await response.json(); - if (nameData?.length > 0) { - return nameData[0].name; - } else { - return ""; - } - } -async function getKeyPair() { - const res = await getData("keyPair").catch(() => null); if (res) { - return res - } else { - throw new Error("Wallet not authenticated"); - } + return res; + } else { + throw new Error('No wallet saved'); } - const getPublicKeys = async (groupNumber: number) => { - const validApi = await getBaseApi(); - const response = await fetch(`${validApi}/groups/members/${groupNumber}?limit=0`); - const groupData = await response.json(); - - if (groupData && Array.isArray(groupData.members)) { - // Use the request queue for fetching public keys - const memberPromises = groupData.members - .filter((member) => member.member) - .map((member) => - requestQueueGetPublicKeys.enqueue(async () => { - const resAddress = await fetch(`${validApi}/addresses/${member.member}`); - const resData = await resAddress.json(); - return resData.publicKey; - }) - ); - - const members = await Promise.all(memberPromises); - return members; - } - - return []; - }; +} - export const getPublicKeysByAddress = async (admins: string[]) => { - const validApi = await getBaseApi(); - - if (Array.isArray(admins)) { - // Use the request queue to limit concurrent fetches - const memberPromises = admins - .filter((address) => address) // Ensure the address is valid - .map((address) => - requestQueueGetPublicKeys.enqueue(async () => { - const resAddress = await fetch(`${validApi}/addresses/${address}`); - const resData = await resAddress.json(); - return resData.publicKey; - }) - ); - - const members = await Promise.all(memberPromises); - return members; - } - - return []; // Return empty array if admins is not an array - }; +export async function getNameInfo() { + const wallet = await getSaveWallet(); + const address = wallet.address0; + const validApi = await getBaseApi(); + const response = await fetch(validApi + '/names/address/' + address); + const nameData = await response.json(); + if (nameData?.length > 0) { + return nameData[0].name; + } else { + return ''; + } +} +async function getKeyPair() { + const res = await getData('keyPair').catch(() => null); + if (res) { + return res; + } else { + throw new Error('Wallet not authenticated'); + } +} +const getPublicKeys = async (groupNumber: number) => { + const validApi = await getBaseApi(); + const response = await fetch( + `${validApi}/groups/members/${groupNumber}?limit=0` + ); + const groupData = await response.json(); - -export const encryptAndPublishSymmetricKeyGroupChat = async ({groupId, previousData}: { - groupId: number, - previousData: Object, -}) => { - try { - - let highestKey = 0 - if(previousData){ - highestKey = Math.max(...Object.keys((previousData || {})).filter(item=> !isNaN(+item)).map(Number)); - - } - - const resKeyPair = await getKeyPair() - const parsedData = resKeyPair - const privateKey = parsedData.privateKey - const userPublicKey = parsedData.publicKey - const groupmemberPublicKeys = await getPublicKeys(groupId) - const symmetricKey = createSymmetricKeyAndNonce() - const nextNumber = highestKey + 1 - const objectToSave = { - ...previousData, - [nextNumber]: symmetricKey - } - - const symmetricKeyAndNonceBase64 = await objectToBase64(objectToSave) - - const encryptedData = encryptDataGroup({ - data64: symmetricKeyAndNonceBase64, - publicKeys: groupmemberPublicKeys, - privateKey, - userPublicKey + if (groupData && Array.isArray(groupData.members)) { + // Use the request queue for fetching public keys + const memberPromises = groupData.members + .filter((member) => member.member) + .map((member) => + requestQueueGetPublicKeys.enqueue(async () => { + const resAddress = await fetch( + `${validApi}/addresses/${member.member}` + ); + const resData = await resAddress.json(); + return resData.publicKey; }) - if(encryptedData){ - const registeredName = await getNameInfo() - const data = await publishData({ - registeredName, file: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `symmetric-qchat-group-${groupId}`, uploadType: 'file', isBase64: true, withFee: true - }) - return { - data, - numberOfMembers: groupmemberPublicKeys.length - } - - } else { - throw new Error('Cannot encrypt content') - } - } catch (error: any) { - throw new Error(error.message); + ); + + const members = await Promise.all(memberPromises); + return members; + } + + return []; +}; + +export const getPublicKeysByAddress = async (admins: string[]) => { + const validApi = await getBaseApi(); + + if (Array.isArray(admins)) { + // Use the request queue to limit concurrent fetches + const memberPromises = admins + .filter((address) => address) // Ensure the address is valid + .map((address) => + requestQueueGetPublicKeys.enqueue(async () => { + const resAddress = await fetch(`${validApi}/addresses/${address}`); + const resData = await resAddress.json(); + return resData.publicKey; + }) + ); + + const members = await Promise.all(memberPromises); + return members; + } + + return []; // Return empty array if admins is not an array +}; + +export const encryptAndPublishSymmetricKeyGroupChat = async ({ + groupId, + previousData, +}: { + groupId: number; + previousData: Object; +}) => { + try { + let highestKey = 0; + if (previousData) { + highestKey = Math.max( + ...Object.keys(previousData || {}) + .filter((item) => !isNaN(+item)) + .map(Number) + ); } -} -export const encryptAndPublishSymmetricKeyGroupChatForAdmins = async ({groupId, previousData, admins}: { - groupId: number, - previousData: Object, + + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const privateKey = parsedData.privateKey; + const userPublicKey = parsedData.publicKey; + const groupmemberPublicKeys = await getPublicKeys(groupId); + const symmetricKey = createSymmetricKeyAndNonce(); + const nextNumber = highestKey + 1; + const objectToSave = { + ...previousData, + [nextNumber]: symmetricKey, + }; + + const symmetricKeyAndNonceBase64 = await objectToBase64(objectToSave); + + const encryptedData = encryptDataGroup({ + data64: symmetricKeyAndNonceBase64, + publicKeys: groupmemberPublicKeys, + privateKey, + userPublicKey, + }); + if (encryptedData) { + const registeredName = await getNameInfo(); + const data = await publishData({ + registeredName, + file: encryptedData, + service: 'DOCUMENT_PRIVATE', + identifier: `symmetric-qchat-group-${groupId}`, + uploadType: 'file', + isBase64: true, + withFee: true, + }); + return { + data, + numberOfMembers: groupmemberPublicKeys.length, + }; + } else { + throw new Error('Cannot encrypt content'); + } + } catch (error: any) { + throw new Error(error.message); + } +}; + +export const encryptAndPublishSymmetricKeyGroupChatForAdmins = async ({ + groupId, + previousData, + admins, +}: { + groupId: number; + previousData: Object; }) => { try { - - let highestKey = 0 - if(previousData){ - highestKey = Math.max(...Object.keys((previousData || {})).filter(item=> !isNaN(+item)).map(Number)); - - } - - const resKeyPair = await getKeyPair() - const parsedData = resKeyPair - const privateKey = parsedData.privateKey - const userPublicKey = parsedData.publicKey - const groupmemberPublicKeys = await getPublicKeysByAddress(admins.map((admin)=> admin.address)) + let highestKey = 0; + if (previousData) { + highestKey = Math.max( + ...Object.keys(previousData || {}) + .filter((item) => !isNaN(+item)) + .map(Number) + ); + } - - const symmetricKey = createSymmetricKeyAndNonce() - const nextNumber = highestKey + 1 - const objectToSave = { - ...previousData, - [nextNumber]: symmetricKey - } - - const symmetricKeyAndNonceBase64 = await objectToBase64(objectToSave) - - const encryptedData = encryptDataGroup({ - data64: symmetricKeyAndNonceBase64, - publicKeys: groupmemberPublicKeys, - privateKey, - userPublicKey - }) - if(encryptedData){ - const registeredName = await getNameInfo() - const data = await publishData({ - registeredName, file: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `admins-symmetric-qchat-group-${groupId}`, uploadType: 'file', isBase64: true, withFee: true - }) - return { - data, - numberOfMembers: groupmemberPublicKeys.length - } - - } else { - throw new Error('Cannot encrypt content') - } + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const privateKey = parsedData.privateKey; + const userPublicKey = parsedData.publicKey; + const groupmemberPublicKeys = await getPublicKeysByAddress( + admins.map((admin) => admin.address) + ); + + const symmetricKey = createSymmetricKeyAndNonce(); + const nextNumber = highestKey + 1; + const objectToSave = { + ...previousData, + [nextNumber]: symmetricKey, + }; + + const symmetricKeyAndNonceBase64 = await objectToBase64(objectToSave); + + const encryptedData = encryptDataGroup({ + data64: symmetricKeyAndNonceBase64, + publicKeys: groupmemberPublicKeys, + privateKey, + userPublicKey, + }); + if (encryptedData) { + const registeredName = await getNameInfo(); + const data = await publishData({ + registeredName, + file: encryptedData, + service: 'DOCUMENT_PRIVATE', + identifier: `admins-symmetric-qchat-group-${groupId}`, + uploadType: 'file', + isBase64: true, + withFee: true, + }); + return { + data, + numberOfMembers: groupmemberPublicKeys.length, + }; + } else { + throw new Error('Cannot encrypt content'); + } } catch (error: any) { - throw new Error(error.message); + throw new Error(error.message); } -} -export const publishGroupEncryptedResource = async ({encryptedData, identifier}) => { - try { - - if(encryptedData && identifier){ - const registeredName = await getNameInfo() - if(!registeredName) throw new Error('You need a name to publish') - const data = await publishData({ - registeredName, file: encryptedData, service: 'DOCUMENT', identifier, uploadType: 'file', isBase64: true, withFee: true - }) - return data - - } else { - throw new Error('Cannot encrypt content') - } - } catch (error: any) { - throw new Error(error.message); - } -} -export const publishOnQDN = async ({data, identifier, service, title, - description, - category, - tag1, - tag2, - tag3, - tag4, - tag5, - uploadType = 'file' +}; + +export const publishGroupEncryptedResource = async ({ + encryptedData, + identifier, }) => { + try { + if (encryptedData && identifier) { + const registeredName = await getNameInfo(); + if (!registeredName) throw new Error('You need a name to publish'); + const data = await publishData({ + registeredName, + file: encryptedData, + service: 'DOCUMENT', + identifier, + uploadType: 'file', + isBase64: true, + withFee: true, + }); + return data; + } else { + throw new Error('Cannot encrypt content'); + } + } catch (error: any) { + throw new Error(error.message); + } +}; - if(data && service){ - const registeredName = await getNameInfo() - if(!registeredName) throw new Error('You need a name to publish') - - const res = await publishData({ - registeredName, file: data, service, identifier, uploadType, isBase64: true, withFee: true, title, - description, - category, - tag1, - tag2, - tag3, - tag4, - tag5 - - }) - return res +export const publishOnQDN = async ({ + data, + identifier, + service, + title, + description, + category, + tag1, + tag2, + tag3, + tag4, + tag5, + uploadType = 'file', +}) => { + if (data && service) { + const registeredName = await getNameInfo(); + if (!registeredName) throw new Error('You need a name to publish'); - - - } else { - throw new Error('Cannot publish content') - } - -} + const res = await publishData({ + registeredName, + file: data, + service, + identifier, + uploadType, + isBase64: true, + withFee: true, + title, + description, + category, + tag1, + tag2, + tag3, + tag4, + tag5, + }); + return res; + } else { + throw new Error('Cannot publish content'); + } +}; export function uint8ArrayToBase64(uint8Array: any) { - const length = uint8Array.length - let binaryString = '' - const chunkSize = 1024 * 1024; // Process 1MB at a time - for (let i = 0; i < length; i += chunkSize) { - const chunkEnd = Math.min(i + chunkSize, length) - const chunk = uint8Array.subarray(i, chunkEnd) + const length = uint8Array.length; + let binaryString = ''; + const chunkSize = 1024 * 1024; // Process 1MB at a time + for (let i = 0; i < length; i += chunkSize) { + const chunkEnd = Math.min(i + chunkSize, length); + const chunk = uint8Array.subarray(i, chunkEnd); - // @ts-ignore - binaryString += Array.from(chunk, byte => String.fromCharCode(byte)).join('') - } - return btoa(binaryString) + // @ts-ignore + binaryString += Array.from(chunk, (byte) => String.fromCharCode(byte)).join( + '' + ); + } + return btoa(binaryString); } export function base64ToUint8Array(base64: string) { - const binaryString = atob(base64) - const len = binaryString.length - const bytes = new Uint8Array(len) + const binaryString = atob(base64); + const len = binaryString.length; + const bytes = new Uint8Array(len); - for (let i = 0; i < len; i++) { - bytes[i] = binaryString.charCodeAt(i) - } - - return bytes + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); } -export const decryptGroupEncryption = async ({data}: { - data: string -}) => { - try { - const resKeyPair = await getKeyPair() - const parsedData = resKeyPair - const privateKey = parsedData.privateKey - const encryptedData = decryptGroupData( - data, - privateKey, - ) - return { - data: uint8ArrayToBase64(encryptedData.decryptedData), - count: encryptedData.count - } - } catch (error: any) { - throw new Error(error.message); - } + return bytes; } +export const decryptGroupEncryption = async ({ data }: { data: string }) => { + try { + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const privateKey = parsedData.privateKey; + const encryptedData = decryptGroupData(data, privateKey); + return { + data: uint8ArrayToBase64(encryptedData.decryptedData), + count: encryptedData.count, + }; + } catch (error: any) { + throw new Error(error.message); + } +}; + export function uint8ArrayToObject(uint8Array: any) { - // Decode the byte array using TextDecoder - const decoder = new TextDecoder() - const jsonString = decoder.decode(uint8Array) - // Convert the JSON string back into an object - return JSON.parse(jsonString) -} \ No newline at end of file + // Decode the byte array using TextDecoder + const decoder = new TextDecoder(); + const jsonString = decoder.decode(uint8Array); + // Convert the JSON string back into an object + return JSON.parse(jsonString); +} diff --git a/src/common/BoundedNumericTextField.tsx b/src/common/BoundedNumericTextField.tsx index 9951d7e..fa0d4b1 100644 --- a/src/common/BoundedNumericTextField.tsx +++ b/src/common/BoundedNumericTextField.tsx @@ -1,9 +1,4 @@ -import { - IconButton, - InputAdornment, - TextField, - TextFieldProps, -} from '@mui/material'; +import { IconButton, InputAdornment, TextFieldProps } from '@mui/material'; import React, { useRef, useState } from 'react'; import AddIcon from '@mui/icons-material/Add'; import RemoveIcon from '@mui/icons-material/Remove'; @@ -44,6 +39,7 @@ export const BoundedNumericTextField = ({ const stringIsEmpty = (value: string) => { return value === ''; }; + const isAllZerosNum = /^0*\.?0*$/; const isFloatNum = /^-?[0-9]*\.?[0-9]*$/; const isIntegerNum = /^-?[0-9]+$/; @@ -85,6 +81,7 @@ export const BoundedNumericTextField = ({ } return value; }; + const filterValue = (value: string) => { if (stringIsEmpty(value)) return ''; value = filterTypes(value); @@ -120,6 +117,7 @@ export const BoundedNumericTextField = ({ setTextFieldValue(value); }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { onChange, ...noChangeProps } = { ...props }; return ( diff --git a/src/components/Apps/AppInfo.tsx b/src/components/Apps/AppInfo.tsx index ad36163..53a155a 100644 --- a/src/components/Apps/AppInfo.tsx +++ b/src/components/Apps/AppInfo.tsx @@ -1,15 +1,12 @@ -import React, { useEffect, useMemo, useState } from "react"; import { AppCircle, AppCircleContainer, - AppCircleLabel, AppDownloadButton, AppDownloadButtonText, AppInfoAppName, AppInfoSnippetContainer, AppInfoSnippetLeft, AppInfoSnippetMiddle, - AppInfoSnippetRight, AppInfoUserName, AppsCategoryInfo, AppsCategoryInfoLabel, @@ -17,193 +14,228 @@ import { AppsCategoryInfoValue, AppsInfoDescription, AppsLibraryContainer, - AppsParent, AppsWidthLimiter, -} from "./Apps-styles"; -import { Avatar, Box, ButtonBase, InputBase } from "@mui/material"; -import { Add } from "@mui/icons-material"; -import { getBaseApiReact, isMobile } from "../../App"; -import LogoSelected from "../../assets/svgs/LogoSelected.svg"; - -import { Spacer } from "../../common/Spacer"; -import { executeEvent } from "../../utils/events"; -import { AppRating } from "./AppRating"; -import { settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom } from "../../atoms/global"; -import { saveToLocalStorage } from "./AppsNavBar"; -import { useRecoilState, useSetRecoilState } from "recoil"; +} from './Apps-styles'; +import { Avatar, Box, useTheme } from '@mui/material'; +import { getBaseApiReact } from '../../App'; +import LogoSelected from '../../assets/svgs/LogoSelected.svg'; +import { Spacer } from '../../common/Spacer'; +import { executeEvent } from '../../utils/events'; +import { AppRating } from './AppRating'; +import { + settingsLocalLastUpdatedAtom, + sortablePinnedAppsAtom, +} from '../../atoms/global'; +import { saveToLocalStorage } from './AppsNavBarDesktop'; +import { useRecoilState, useSetRecoilState } from 'recoil'; export const AppInfo = ({ app, myName }) => { - const isInstalled = app?.status?.status === "READY"; - const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(sortablePinnedAppsAtom); + const isInstalled = app?.status?.status === 'READY'; + const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState( + sortablePinnedAppsAtom + ); + const theme = useTheme(); - const isSelectedAppPinned = !!sortablePinnedApps?.find((item)=> item?.name === app?.name && item?.service === app?.service) - const setSettingsLocalLastUpdated = useSetRecoilState(settingsLocalLastUpdatedAtom); + const isSelectedAppPinned = !!sortablePinnedApps?.find( + (item) => item?.name === app?.name && item?.service === app?.service + ); + const setSettingsLocalLastUpdated = useSetRecoilState( + settingsLocalLastUpdatedAtom + ); return ( - + + - - {!isMobile && } - - - - + + - - - center-icon - - - - - - {app?.metadata?.title || app?.name} - - - {app?.name} - - - - - - - - { - setSortablePinnedApps((prev) => { - let updatedApps; - - if (isSelectedAppPinned) { - // Remove the selected app if it is pinned - updatedApps = prev.filter( - (item) => !(item?.name === app?.name && item?.service === app?.service) + alt={app?.name} + src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${ + app?.name + }/qortal_avatar?async=true`} + > + center-icon + + + + + + + {app?.metadata?.title || app?.name} + + + + + {app?.name} + + + + + + + + + { + setSortablePinnedApps((prev) => { + let updatedApps; + + if (isSelectedAppPinned) { + // Remove the selected app if it is pinned + updatedApps = prev.filter( + (item) => + !( + item?.name === app?.name && + item?.service === app?.service + ) + ); + } else { + // Add the selected app if it is not pinned + updatedApps = [ + ...prev, + { + name: app?.name, + service: app?.service, + }, + ]; + } + + saveToLocalStorage( + 'ext_saved_settings', + 'sortablePinnedApps', + updatedApps ); - } else { - // Add the selected app if it is not pinned - updatedApps = [...prev, { - name: app?.name, - service: app?.service, - }]; - } - - saveToLocalStorage('ext_saved_settings', 'sortablePinnedApps', updatedApps) - return updatedApps; - }); - setSettingsLocalLastUpdated(Date.now()) - }} - sx={{ - backgroundColor: "#359ff7ff", - width: "100%", - maxWidth: "320px", - height: "29px", - opacity: isSelectedAppPinned ? 0.6 : 1 - }} - > - - {!isMobile ? ( - <> - {isSelectedAppPinned ? 'Unpin from dashboard' : 'Pin to dashboard'} - - ) : ( - <> - {isSelectedAppPinned ? 'Unpin' : 'Pin'} - - )} - - - - { - executeEvent("addTab", { - data: app, - }); - }} - sx={{ - backgroundColor: isInstalled ? "#0091E1" : "#247C0E", - width: "100%", - maxWidth: "320px", - height: "29px", - }} - > - - {isInstalled ? "Open" : "Download"} - - - - - - - - - - - - - - Category: - - - {app?.metadata?.categoryName || "none"} - - - - - About this Q-App - - - - {app?.metadata?.description || "No description"} - + return updatedApps; + }); + setSettingsLocalLastUpdated(Date.now()); + }} + sx={{ + backgroundColor: theme.palette.background.paper, + height: '29px', + maxWidth: '320px', + opacity: isSelectedAppPinned ? 0.6 : 1, + width: '100%', + }} + > + + {isSelectedAppPinned + ? 'Unpin from dashboard' + : 'Pin to dashboard'} + + + + { + executeEvent('addTab', { + data: app, + }); + }} + sx={{ + backgroundColor: isInstalled + ? '#0091E1' + : theme.palette.background.paper, + height: '29px', + maxWidth: '320px', + width: '100%', + }} + > + + {isInstalled ? 'Open' : 'Download'} + + + + + + + + + + + + + + + + + + + Category: + + + + + {app?.metadata?.categoryName || 'none'} + + + + + + + About this Q-App + + + + + + {app?.metadata?.description || 'No description'} + ); diff --git a/src/components/Apps/AppInfoSnippet.tsx b/src/components/Apps/AppInfoSnippet.tsx index 4870474..c877bba 100644 --- a/src/components/Apps/AppInfoSnippet.tsx +++ b/src/components/Apps/AppInfoSnippet.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { AppCircle, AppCircleContainer, @@ -10,148 +9,186 @@ import { AppInfoSnippetMiddle, AppInfoSnippetRight, AppInfoUserName, -} from "./Apps-styles"; -import { Avatar, ButtonBase } from "@mui/material"; -import { getBaseApiReact, isMobile } from "../../App"; -import LogoSelected from "../../assets/svgs/LogoSelected.svg"; +} from './Apps-styles'; +import { Avatar, ButtonBase, useTheme } from '@mui/material'; +import { getBaseApiReact } from '../../App'; +import LogoSelected from '../../assets/svgs/LogoSelected.svg'; +import { Spacer } from '../../common/Spacer'; +import { executeEvent } from '../../utils/events'; +import { AppRating } from './AppRating'; +import { useRecoilState, useSetRecoilState } from 'recoil'; +import { + settingsLocalLastUpdatedAtom, + sortablePinnedAppsAtom, +} from '../../atoms/global'; +import { saveToLocalStorage } from './AppsNavBarDesktop'; -import { Spacer } from "../../common/Spacer"; -import { executeEvent } from "../../utils/events"; -import { AppRating } from "./AppRating"; -import { useRecoilState, useSetRecoilState } from "recoil"; -import { settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom } from "../../atoms/global"; -import { saveToLocalStorage } from "./AppsNavBar"; +export const AppInfoSnippet = ({ + app, + myName, + isFromCategory, + parentStyles = {}, +}) => { + const isInstalled = app?.status?.status === 'READY'; + const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState( + sortablePinnedAppsAtom + ); -export const AppInfoSnippet = ({ app, myName, isFromCategory, parentStyles = {} }) => { + const isSelectedAppPinned = !!sortablePinnedApps?.find( + (item) => item?.name === app?.name && item?.service === app?.service + ); + const setSettingsLocalLastUpdated = useSetRecoilState( + settingsLocalLastUpdatedAtom + ); + const theme = useTheme(); - const isInstalled = app?.status?.status === 'READY' - const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(sortablePinnedAppsAtom); - - const isSelectedAppPinned = !!sortablePinnedApps?.find((item)=> item?.name === app?.name && item?.service === app?.service) - const setSettingsLocalLastUpdated = useSetRecoilState(settingsLocalLastUpdatedAtom); return ( - + - { - if(isFromCategory){ - executeEvent("selectedAppInfoCategory", { + { + if (isFromCategory) { + executeEvent('selectedAppInfoCategory', { + data: app, + }); + return; + } + executeEvent('selectedAppInfo', { data: app, }); - return - } - executeEvent("selectedAppInfo", { - data: app, - }); - }} - > - - + + + + center-icon + + + + + + + { + if (isFromCategory) { + executeEvent('selectedAppInfoCategory', { + data: app, + }); + return; + } + executeEvent('selectedAppInfo', { + data: app, + }); }} > - - center-icon - - - - - - - { - if(isFromCategory){ - executeEvent("selectedAppInfoCategory", { + {app?.metadata?.title || app?.name} + + + + + {app?.name} + + + + + + + + + { + setSortablePinnedApps((prev) => { + let updatedApps; + + if (isSelectedAppPinned) { + // Remove the selected app if it is pinned + updatedApps = prev.filter( + (item) => + !( + item?.name === app?.name && item?.service === app?.service + ) + ); + } else { + // Add the selected app if it is not pinned + updatedApps = [ + ...prev, + { + name: app?.name, + service: app?.service, + }, + ]; + } + + saveToLocalStorage( + 'ext_saved_settings', + 'sortablePinnedApps', + updatedApps + ); + return updatedApps; + }); + setSettingsLocalLastUpdated(Date.now()); + }} + sx={{ + backgroundColor: theme.palette.background.paper, + opacity: isSelectedAppPinned ? 0.6 : 1, + }} + > + + {' '} + {isSelectedAppPinned ? 'Unpin' : 'Pin'} + + + + { + executeEvent('addTab', { data: app, }); - return - } - executeEvent("selectedAppInfo", { - data: app, - }); - }}> - - {app?.metadata?.title || app?.name} - - - - - { app?.name} - - - - - - - {!isMobile && ( - { - - setSortablePinnedApps((prev) => { - let updatedApps; - - if (isSelectedAppPinned) { - // Remove the selected app if it is pinned - updatedApps = prev.filter( - (item) => !(item?.name === app?.name && item?.service === app?.service) - ); - } else { - // Add the selected app if it is not pinned - updatedApps = [...prev, { - name: app?.name, - service: app?.service, - }]; - } - - saveToLocalStorage('ext_saved_settings', 'sortablePinnedApps', updatedApps) - return updatedApps; - }); - setSettingsLocalLastUpdated(Date.now()) - }} sx={{ - backgroundColor: '#359ff7ff', - opacity: isSelectedAppPinned ? 0.6 : 1 - - }}> - {isSelectedAppPinned ? 'Unpin' : 'Pin'} - - )} - - { - - executeEvent("addTab", { - data: app - }) - }} sx={{ - backgroundColor: isInstalled ? '#0091E1' : '#247C0E', - - }}> - {isInstalled ? 'Open' : 'Download'} + }} + sx={{ + backgroundColor: isInstalled + ? '#0091E1' + : theme.palette.background.paper, + }} + > + + {isInstalled ? 'Open' : 'Download'} + diff --git a/src/components/Apps/AppPublish.tsx b/src/components/Apps/AppPublish.tsx index 76bde28..62cc098 100644 --- a/src/components/Apps/AppPublish.tsx +++ b/src/components/Apps/AppPublish.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useMemo, useState } from "react"; +import React, { useContext, useEffect, useState } from 'react'; import { AppCircle, AppCircleContainer, @@ -19,90 +19,74 @@ import { PublishQAppCTAButton, PublishQAppChoseFile, PublishQAppInfo, -} from "./Apps-styles"; -import { - Avatar, - Box, - ButtonBase, - InputBase, - InputLabel, - MenuItem, - Select, -} from "@mui/material"; -import { - Select as BaseSelect, - SelectProps, - selectClasses, - SelectRootSlotProps, -} from "@mui/base/Select"; -import { Option as BaseOption, optionClasses } from "@mui/base/Option"; -import { styled } from "@mui/system"; -import UnfoldMoreRoundedIcon from "@mui/icons-material/UnfoldMoreRounded"; -import { Add } from "@mui/icons-material"; -import { MyContext, getBaseApiReact, isMobile } from "../../App"; -import LogoSelected from "../../assets/svgs/LogoSelected.svg"; - -import { Spacer } from "../../common/Spacer"; -import { executeEvent } from "../../utils/events"; -import { useDropzone } from "react-dropzone"; -import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar"; -import { CustomizedSnackbars } from "../Snackbar/Snackbar"; -import { getFee } from "../../background"; -import { fileToBase64 } from "../../utils/fileReading"; +} from './Apps-styles'; +import { InputBase, InputLabel, MenuItem, Select } from '@mui/material'; +import { styled } from '@mui/system'; +import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded'; +import { Add } from '@mui/icons-material'; +import { MyContext, getBaseApiReact } from '../../App'; +import LogoSelected from '../../assets/svgs/LogoSelected.svg'; +import { Spacer } from '../../common/Spacer'; +import { executeEvent } from '../../utils/events'; +import { useDropzone } from 'react-dropzone'; +import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; +import { CustomizedSnackbars } from '../Snackbar/Snackbar'; +import { getFee } from '../../background'; +import { fileToBase64 } from '../../utils/fileReading'; const CustomSelect = styled(Select)({ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100%", - maxWidth: "450px", - "& .MuiSelect-select": { - padding: "0px", + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100%', + maxWidth: '450px', + '& .MuiSelect-select': { + padding: '0px', }, - "&:hover": { - borderColor: "none", // Border color on hover + '&:hover': { + borderColor: 'none', // Border color on hover }, - "&.Mui-focused .MuiOutlinedInput-notchedOutline": { - borderColor: "none", // Border color when focused + '&.Mui-focused .MuiOutlinedInput-notchedOutline': { + borderColor: 'none', // Border color when focused }, - "&.Mui-disabled": { + '&.Mui-disabled': { opacity: 0.5, // Lower opacity when disabled }, - "& .MuiSvgIcon-root": { - color: "var(--50-white, #FFFFFF80)", + '& .MuiSvgIcon-root': { + color: 'var(--50-white, #FFFFFF80)', }, }); const CustomMenuItem = styled(MenuItem)({ - backgroundColor: "#1f1f1f", // Background for dropdown items - color: "#ccc", - "&:hover": { - backgroundColor: "#333", // Darker background on hover + backgroundColor: '#1f1f1f', // Background for dropdown items + color: '#ccc', + '&:hover': { + backgroundColor: '#333', // Darker background on hover }, }); export const AppPublish = ({ names, categories }) => { - const [name, setName] = useState(""); - const [title, setTitle] = useState(""); - const [description, setDescription] = useState(""); - const [category, setCategory] = useState(""); - const [appType, setAppType] = useState("APP"); + const [name, setName] = useState(''); + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [category, setCategory] = useState(''); + const [appType, setAppType] = useState('APP'); const [file, setFile] = useState(null); const { show } = useContext(MyContext); - const [tag1, setTag1] = useState(""); - const [tag2, setTag2] = useState(""); - const [tag3, setTag3] = useState(""); - const [tag4, setTag4] = useState(""); - const [tag5, setTag5] = useState(""); + const [tag1, setTag1] = useState(''); + const [tag2, setTag2] = useState(''); + const [tag3, setTag3] = useState(''); + const [tag4, setTag4] = useState(''); + const [tag5, setTag5] = useState(''); const [openSnack, setOpenSnack] = useState(false); const [infoSnack, setInfoSnack] = useState(null); - const [isLoading, setIsLoading] = useState(""); - const maxFileSize = appType === "APP" ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB + const [isLoading, setIsLoading] = useState(''); + const maxFileSize = appType === 'APP' ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB const { getRootProps, getInputProps } = useDropzone({ accept: { - "application/zip": [".zip"], // Only accept zip files + 'application/zip': ['.zip'], // Only accept zip files }, maxSize: maxFileSize, // Set the max size based on appType multiple: false, // Disable multiple file uploads @@ -114,7 +98,7 @@ export const AppPublish = ({ names, categories }) => { onDropRejected: (fileRejections) => { fileRejections.forEach(({ file, errors }) => { errors.forEach((error) => { - if (error.code === "file-too-large") { + if (error.code === 'file-too-large') { console.error( `File ${file.name} is too large. Max size allowed is ${ maxFileSize / (1024 * 1024) @@ -128,13 +112,13 @@ export const AppPublish = ({ names, categories }) => { const getQapp = React.useCallback(async (name, appType) => { try { - setIsLoading("Loading app information"); + setIsLoading('Loading app information'); const url = `${getBaseApiReact()}/arbitrary/resources/search?service=${appType}&mode=ALL&name=${name}&includemetadata=true`; const response = await fetch(url, { - method: "GET", + method: 'GET', headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, }); if (!response?.ok) return; @@ -142,18 +126,18 @@ export const AppPublish = ({ names, categories }) => { if (responseData?.length > 0) { const myApp = responseData[0]; - setTitle(myApp?.metadata?.title || ""); - setDescription(myApp?.metadata?.description || ""); - setCategory(myApp?.metadata?.category || ""); - setTag1(myApp?.metadata?.tags[0] || ""); - setTag2(myApp?.metadata?.tags[1] || ""); - setTag3(myApp?.metadata?.tags[2] || ""); - setTag4(myApp?.metadata?.tags[3] || ""); - setTag5(myApp?.metadata?.tags[4] || ""); + setTitle(myApp?.metadata?.title || ''); + setDescription(myApp?.metadata?.description || ''); + setCategory(myApp?.metadata?.category || ''); + setTag1(myApp?.metadata?.tags[0] || ''); + setTag2(myApp?.metadata?.tags[1] || ''); + setTag3(myApp?.metadata?.tags[2] || ''); + setTag4(myApp?.metadata?.tags[3] || ''); + setTag5(myApp?.metadata?.tags[4] || ''); } } catch (error) { } finally { - setIsLoading(""); + setIsLoading(''); } }, []); @@ -173,12 +157,12 @@ export const AppPublish = ({ names, categories }) => { file, }; const requiredFields = [ - "name", - "title", - "description", - "category", - "appType", - "file", + 'name', + 'title', + 'description', + 'category', + 'appType', + 'file', ]; const missingFields: string[] = []; @@ -188,32 +172,33 @@ export const AppPublish = ({ names, categories }) => { } }); if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(", "); + const missingFieldsString = missingFields.join(', '); const errorMsg = `Missing fields: ${missingFieldsString}`; throw new Error(errorMsg); } - const fee = await getFee("ARBITRARY"); + const fee = await getFee('ARBITRARY'); await show({ - message: "Would you like to publish this app?", - publishFee: fee.fee + " QORT", + message: 'Would you like to publish this app?', + publishFee: fee.fee + ' QORT', }); - setIsLoading("Publishing... Please wait."); + setIsLoading('Publishing... Please wait.'); const fileBase64 = await fileToBase64(file); await new Promise((res, rej) => { - window.sendMessage("publishOnQDN", { - data: fileBase64, - service: appType, - title, - description, - category, - tag1, - tag2, - tag3, - tag4, - tag5, - uploadType: "zip", - }) + window + .sendMessage('publishOnQDN', { + data: fileBase64, + service: appType, + title, + description, + category, + tag1, + tag2, + tag3, + tag4, + tag5, + uploadType: 'zip', + }) .then((response) => { if (!response?.error) { res(response); @@ -222,14 +207,13 @@ export const AppPublish = ({ names, categories }) => { rej(response.error); }) .catch((error) => { - rej(error.message || "An error occurred"); + rej(error.message || 'An error occurred'); }); - }); setInfoSnack({ - type: "success", + type: 'success', message: - "Successfully published. Please wait a couple minutes for the network to propogate the changes.", + 'Successfully published. Please wait a couple minutes for the network to propogate the changes.', }); setOpenSnack(true); const dataObj = { @@ -242,35 +226,49 @@ export const AppPublish = ({ names, categories }) => { }, created: Date.now(), }; - executeEvent("addTab", { + executeEvent('addTab', { data: dataObj, }); } catch (error) { setInfoSnack({ - type: "error", - message: error?.message || "Unable to publish app", + type: 'error', + message: error?.message || 'Unable to publish app', }); setOpenSnack(true); } finally { - setIsLoading(""); + setIsLoading(''); } }; + return ( - - + + Create Apps! + + Note: Currently, only one App and Website is allowed per Name. + - Name/App + + + Name/App + + { Select Name/App - {" "} + {' '} {/* This is the placeholder item */} {names.map((name) => { return {name}; })} + - App service type + + + App service type + + { Select App Type - {" "} + {' '} {/* This is the placeholder item */} - App - Website + App + Website + - Title + + + Title + + setTitle(e.target.value)} sx={{ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100%", - maxWidth: "450px", + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100%', + maxWidth: '450px', }} placeholder="Title" inputProps={{ - "aria-label": "Title", - fontSize: "14px", - fontWeight: 400, - }} - /> - - Description - setDescription(e.target.value)} - sx={{ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100%", - maxWidth: "450px", - }} - placeholder="Description" - inputProps={{ - "aria-label": "Description", - fontSize: "14px", + 'aria-label': 'Title', + fontSize: '14px', fontWeight: 400, }} /> - Category + + + Description + + + setDescription(e.target.value)} + sx={{ + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100%', + maxWidth: '450px', + }} + placeholder="Description" + inputProps={{ + 'aria-label': 'Description', + fontSize: '14px', + fontWeight: 400, + }} + /> + + + + + Category + + { Select Category - {" "} + {' '} {/* This is the placeholder item */} {categories?.map((category) => { @@ -379,23 +404,30 @@ export const AppPublish = ({ names, categories }) => { ); })} + - Tags + + + Tags + + setTag1(e.target.value)} sx={{ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100px", + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100px', }} placeholder="Tag 1" inputProps={{ - "aria-label": "Tag 1", - fontSize: "14px", + 'aria-label': 'Tag 1', + fontSize: '14px', fontWeight: 400, }} /> @@ -403,16 +435,16 @@ export const AppPublish = ({ names, categories }) => { value={tag2} onChange={(e) => setTag2(e.target.value)} sx={{ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100px", + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100px', }} placeholder="Tag 2" inputProps={{ - "aria-label": "Tag 2", - fontSize: "14px", + 'aria-label': 'Tag 2', + fontSize: '14px', fontWeight: 400, }} /> @@ -420,16 +452,16 @@ export const AppPublish = ({ names, categories }) => { value={tag3} onChange={(e) => setTag3(e.target.value)} sx={{ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100px", + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100px', }} placeholder="Tag 3" inputProps={{ - "aria-label": "Tag 3", - fontSize: "14px", + 'aria-label': 'Tag 3', + fontSize: '14px', fontWeight: 400, }} /> @@ -437,16 +469,16 @@ export const AppPublish = ({ names, categories }) => { value={tag4} onChange={(e) => setTag4(e.target.value)} sx={{ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100px", + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100px', }} placeholder="Tag 4" inputProps={{ - "aria-label": "Tag 4", - fontSize: "14px", + 'aria-label': 'Tag 4', + fontSize: '14px', fontWeight: 400, }} /> @@ -454,27 +486,31 @@ export const AppPublish = ({ names, categories }) => { value={tag5} onChange={(e) => setTag5(e.target.value)} sx={{ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100px", + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100px', }} placeholder="Tag 5" inputProps={{ - "aria-label": "Tag 5", - fontSize: "14px", + 'aria-label': 'Tag 5', + fontSize: '14px', fontWeight: 400, }} /> + + - Select .zip file containing static content:{" "} + Select .zip file containing static content:{' '} + + {`(${ - appType === "APP" ? "50mb" : "400mb" + appType === 'APP' ? '50mb' : '400mb' } MB maximum)`} {file && ( <> @@ -484,21 +520,25 @@ export const AppPublish = ({ names, categories }) => { )} + - {" "} + {' '} Choose File + + Publish + { info={infoSnack} setInfo={setInfoSnack} /> - ); }; diff --git a/src/components/Apps/AppRating.tsx b/src/components/Apps/AppRating.tsx index abf1bba..191650d 100644 --- a/src/components/Apps/AppRating.tsx +++ b/src/components/Apps/AppRating.tsx @@ -91,6 +91,7 @@ export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => { } } }, []); + useEffect(() => { if (hasCalledRef.current) return; if (!app) return; @@ -108,6 +109,7 @@ export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => { message: `Would you like to rate this app a rating of ${newValue}?. It will create a POLL tx.`, publishFee: fee.fee + ' QORT', }); + if (hasPublishedRating === false) { const pollName = `app-library-${app.service}-rating-${app.name}`; const pollOptions = [`1, 2, 3, 4, 5, initialValue-${newValue}`]; diff --git a/src/components/Apps/AppViewer.tsx b/src/components/Apps/AppViewer.tsx index 9302df7..723db86 100644 --- a/src/components/Apps/AppViewer.tsx +++ b/src/components/Apps/AppViewer.tsx @@ -1,210 +1,249 @@ -import React, { useContext, useEffect, useMemo, useState } from "react"; +import React, { useContext, useEffect, useMemo, useState } from 'react'; +import { Box } from '@mui/material'; +import { MyContext, getBaseApiReact } from '../../App'; +import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; +import { useFrame } from 'react-frame-component'; +import { useQortalMessageListener } from './useQortalMessageListener'; +import { useThemeContext } from '../Theme/ThemeContext'; -import { Box, } from "@mui/material"; -import { MyContext, getBaseApiReact, isMobile } from "../../App"; +export const AppViewer = React.forwardRef( + ({ app, hide, isDevMode, skipAuth }, iframeRef) => { + const { rootHeight } = useContext(MyContext); + // const iframeRef = useRef(null); + const { window: frameWindow } = useFrame(); + const { path, history, changeCurrentIndex, resetHistory } = + useQortalMessageListener( + frameWindow, + iframeRef, + app?.tabId, + isDevMode, + app?.name, + app?.service, + skipAuth + ); + const [url, setUrl] = useState(''); + const { themeMode } = useThemeContext(); -import { subscribeToEvent, unsubscribeFromEvent } from "../../utils/events"; -import { useFrame } from "react-frame-component"; -import { useQortalMessageListener } from "./useQortalMessageListener"; -import { useThemeContext } from "../Theme/ThemeContext"; + useEffect(() => { + if (app?.isPreview) return; + if (isDevMode) { + setUrl(app?.url); + return; + } + let hasQueryParam = false; + if (app?.path && app.path.includes('?')) { + hasQueryParam = true; + } + setUrl( + `${getBaseApiReact()}/render/${app?.service}/${app?.name}${app?.path != null ? `/${app?.path}` : ''}${hasQueryParam ? '&' : '?'}theme=${themeMode}&identifier=${app?.identifier != null && app?.identifier != 'null' ? app?.identifier : ''}` + ); + }, [app?.service, app?.name, app?.identifier, app?.path, app?.isPreview]); + useEffect(() => { + if (app?.isPreview && app?.url) { + resetHistory(); + setUrl(app.url); + } + }, [app?.url, app?.isPreview]); + const defaultUrl = useMemo(() => { + return url; + }, [url, isDevMode]); - -export const AppViewer = React.forwardRef(({ app , hide, isDevMode, skipAuth}, iframeRef) => { - const { rootHeight } = useContext(MyContext); - // const iframeRef = useRef(null); - const { window: frameWindow } = useFrame(); - const {path, history, changeCurrentIndex, resetHistory} = useQortalMessageListener(frameWindow, iframeRef, app?.tabId, isDevMode, app?.name, app?.service, skipAuth) - const [url, setUrl] = useState('') - const { themeMode } = useThemeContext(); - - useEffect(()=> { - if(app?.isPreview) return - if(isDevMode){ - setUrl(app?.url) - return - } - let hasQueryParam = false - if(app?.path && app.path.includes('?')){ - hasQueryParam = true - } - - setUrl(`${getBaseApiReact()}/render/${app?.service}/${app?.name}${app?.path != null ? `/${app?.path}` : ''}${hasQueryParam ? "&": "?" }theme=${themeMode}&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}`) - }, [app?.service, app?.name, app?.identifier, app?.path, app?.isPreview]) - - useEffect(()=> { - if(app?.isPreview && app?.url){ - resetHistory() - setUrl(app.url) - } - }, [app?.url, app?.isPreview]) - const defaultUrl = useMemo(()=> { - return url - }, [url, isDevMode]) - - - const refreshAppFunc = (e) => { - const {tabId} = e.detail - if(tabId === app?.tabId){ - if(isDevMode){ - resetHistory() - if(!app?.isPreview || app?.isPrivate){ - setUrl(app?.url + `?time=${Date.now()}`) + const refreshAppFunc = (e) => { + const { tabId } = e.detail; + if (tabId === app?.tabId) { + if (isDevMode) { + resetHistory(); + if (!app?.isPreview || app?.isPrivate) { + setUrl(app?.url + `?time=${Date.now()}`); + } + return; } - return - + const constructUrl = `${getBaseApiReact()}/render/${app?.service}/${app?.name}${path != null ? path : ''}?theme=${themeMode}&identifier=${app?.identifier != null ? app?.identifier : ''}&time=${new Date().getMilliseconds()}`; + setUrl(constructUrl); } - const constructUrl = `${getBaseApiReact()}/render/${app?.service}/${app?.name}${path != null ? path : ''}?theme=${themeMode}&identifier=${app?.identifier != null ? app?.identifier : ''}&time=${new Date().getMilliseconds()}` - setUrl(constructUrl) - } - }; - - useEffect(() => { - subscribeToEvent("refreshApp", refreshAppFunc); - - return () => { - unsubscribeFromEvent("refreshApp", refreshAppFunc); }; - }, [app, path, isDevMode]); - useEffect(()=> { - if(!iframeRef?.current) return - const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*"; - // Send the navigation command after setting up the listener and timeout - iframeRef.current.contentWindow.postMessage( - { action: 'THEME_CHANGED', theme: themeMode, requestedHandler: 'UI' }, targetOrigin - ); - }, [themeMode]) + useEffect(() => { + subscribeToEvent('refreshApp', refreshAppFunc); - const removeTrailingSlash = (str) => str.replace(/\/$/, ''); - const copyLinkFunc = (e) => { - const {tabId} = e.detail - if(tabId === app?.tabId){ - let link = 'qortal://' + app?.service + '/' + app?.name - if(path && path.startsWith('/')){ - link = link + removeTrailingSlash(path) - } - if(path && !path.startsWith('/')){ - link = link + '/' + removeTrailingSlash(path) - } - navigator.clipboard.writeText(link) - .then(() => { - console.log("Path copied to clipboard:", path); - }) - .catch((error) => { - console.error("Failed to copy path:", error); - }); - } - }; + return () => { + unsubscribeFromEvent('refreshApp', refreshAppFunc); + }; + }, [app, path, isDevMode]); - useEffect(() => { - subscribeToEvent("copyLink", copyLinkFunc); - - return () => { - unsubscribeFromEvent("copyLink", copyLinkFunc); - }; - }, [app, path]); - - // Function to navigate back in iframe - const navigateBackInIframe = async () => { - if (iframeRef.current && iframeRef.current.contentWindow && history?.currentIndex > 0) { - // Calculate the previous index and path - const previousPageIndex = history.currentIndex - 1; - const previousPath = history.customQDNHistoryPaths[previousPageIndex]; - const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*"; - // Signal non-manual navigation - iframeRef.current.contentWindow.postMessage( - { action: 'PERFORMING_NON_MANUAL', currentIndex: previousPageIndex },targetOrigin - ); - // Update the current index locally - changeCurrentIndex(previousPageIndex); - - // Create a navigation promise with a 200ms timeout - const navigationPromise = new Promise((resolve, reject) => { - function handleNavigationSuccess(event) { - if (event.data?.action === 'NAVIGATION_SUCCESS' && event.data.path === previousPath) { - frameWindow.removeEventListener('message', handleNavigationSuccess); - resolve(); - } - } - - frameWindow.addEventListener('message', handleNavigationSuccess); - - // Timeout after 200ms if no response - setTimeout(() => { - window.removeEventListener('message', handleNavigationSuccess); - reject(new Error("Navigation timeout")); - }, 200); - const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*"; + useEffect(() => { + if (!iframeRef?.current) return; + const targetOrigin = iframeRef.current + ? new URL(iframeRef.current.src).origin + : '*'; // Send the navigation command after setting up the listener and timeout iframeRef.current.contentWindow.postMessage( - { action: 'NAVIGATE_TO_PATH', path: previousPath, requestedHandler: 'UI' }, targetOrigin + { action: 'THEME_CHANGED', theme: themeMode, requestedHandler: 'UI' }, + targetOrigin ); - }); + }, [themeMode]); - // Execute navigation promise and handle timeout fallback - try { - await navigationPromise; - } catch (error) { - if(isDevMode){ - setUrl(`${url}${previousPath != null ? previousPath : ''}?theme=${themeMode}&time=${new Date().getMilliseconds()}&isManualNavigation=false`) - return - } - setUrl(`${getBaseApiReact()}/render/${app?.service}/${app?.name}${previousPath != null ? previousPath : ''}?theme=${themeMode}&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}&time=${new Date().getMilliseconds()}&isManualNavigation=false`) - // iframeRef.current.contentWindow.location.href = previousPath; // Fallback URL update - } - } else { - console.log('Iframe not accessible or does not have a content window.'); - } -}; + const removeTrailingSlash = (str) => str.replace(/\/$/, ''); - const navigateBackAppFunc = (e) => { - - navigateBackInIframe() - }; - - useEffect(() => { - if(!app?.tabId) return - subscribeToEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc); - - return () => { - unsubscribeFromEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc); + const copyLinkFunc = (e) => { + const { tabId } = e.detail; + if (tabId === app?.tabId) { + let link = 'qortal://' + app?.service + '/' + app?.name; + if (path && path.startsWith('/')) { + link = link + removeTrailingSlash(path); + } + if (path && !path.startsWith('/')) { + link = link + '/' + removeTrailingSlash(path); + } + navigator.clipboard + .writeText(link) + .then(() => { + console.log('Path copied to clipboard:', path); + }) + .catch((error) => { + console.error('Failed to copy path:', error); + }); + } }; - }, [app, history]); + useEffect(() => { + subscribeToEvent('copyLink', copyLinkFunc); - // Function to navigate back in iframe - const navigateForwardInIframe = async () => { + return () => { + unsubscribeFromEvent('copyLink', copyLinkFunc); + }; + }, [app, path]); - - if (iframeRef.current && iframeRef.current.contentWindow) { - const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*"; - iframeRef.current.contentWindow.postMessage( - { action: 'NAVIGATE_FORWARD'}, + // Function to navigate back in iframe + const navigateBackInIframe = async () => { + if ( + iframeRef.current && + iframeRef.current.contentWindow && + history?.currentIndex > 0 + ) { + // Calculate the previous index and path + const previousPageIndex = history.currentIndex - 1; + const previousPath = history.customQDNHistoryPaths[previousPageIndex]; + const targetOrigin = iframeRef.current + ? new URL(iframeRef.current.src).origin + : '*'; + // Signal non-manual navigation + iframeRef.current.contentWindow.postMessage( + { action: 'PERFORMING_NON_MANUAL', currentIndex: previousPageIndex }, targetOrigin - ); - } else { - console.log('Iframe not accessible or does not have a content window.'); + ); + // Update the current index locally + changeCurrentIndex(previousPageIndex); + + // Create a navigation promise with a 200ms timeout + const navigationPromise = new Promise((resolve, reject) => { + function handleNavigationSuccess(event) { + if ( + event.data?.action === 'NAVIGATION_SUCCESS' && + event.data.path === previousPath + ) { + frameWindow.removeEventListener( + 'message', + handleNavigationSuccess + ); + resolve(); + } + } + + frameWindow.addEventListener('message', handleNavigationSuccess); + + // Timeout after 200ms if no response + setTimeout(() => { + window.removeEventListener('message', handleNavigationSuccess); + reject(new Error('Navigation timeout')); + }, 200); + const targetOrigin = iframeRef.current + ? new URL(iframeRef.current.src).origin + : '*'; + // Send the navigation command after setting up the listener and timeout + iframeRef.current.contentWindow.postMessage( + { + action: 'NAVIGATE_TO_PATH', + path: previousPath, + requestedHandler: 'UI', + }, + targetOrigin + ); + }); + + // Execute navigation promise and handle timeout fallback + try { + await navigationPromise; + } catch (error) { + if (isDevMode) { + setUrl( + `${url}${previousPath != null ? previousPath : ''}?theme=${themeMode}&time=${new Date().getMilliseconds()}&isManualNavigation=false` + ); + return; + } + setUrl( + `${getBaseApiReact()}/render/${app?.service}/${app?.name}${previousPath != null ? previousPath : ''}?theme=${themeMode}&identifier=${app?.identifier != null && app?.identifier != 'null' ? app?.identifier : ''}&time=${new Date().getMilliseconds()}&isManualNavigation=false` + ); + // iframeRef.current.contentWindow.location.href = previousPath; // Fallback URL update + } + } else { + console.log('Iframe not accessible or does not have a content window.'); + } + }; + + const navigateBackAppFunc = (e) => { + navigateBackInIframe(); + }; + + useEffect(() => { + if (!app?.tabId) return; + subscribeToEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc); + + return () => { + unsubscribeFromEvent( + `navigateBackApp-${app?.tabId}`, + navigateBackAppFunc + ); + }; + }, [app, history]); + + // Function to navigate back in iframe + const navigateForwardInIframe = async () => { + if (iframeRef.current && iframeRef.current.contentWindow) { + const targetOrigin = iframeRef.current + ? new URL(iframeRef.current.src).origin + : '*'; + iframeRef.current.contentWindow.postMessage( + { action: 'NAVIGATE_FORWARD' }, + targetOrigin + ); + } else { + console.log('Iframe not accessible or does not have a content window.'); + } + }; + + return ( + + + + ); } -}; - - - return ( - - - - - - ); -}); +); diff --git a/src/components/Apps/AppViewerContainer.tsx b/src/components/Apps/AppViewerContainer.tsx index 194ae68..daa3a7e 100644 --- a/src/components/Apps/AppViewerContainer.tsx +++ b/src/components/Apps/AppViewerContainer.tsx @@ -1,26 +1,25 @@ -import React, { useContext, } from 'react'; +import React, { useContext } from 'react'; import { AppViewer } from './AppViewer'; import Frame from 'react-frame-component'; -import { MyContext, isMobile } from '../../App'; +import { MyContext } from '../../App'; -const AppViewerContainer = React.forwardRef(({ app, isSelected, hide, isDevMode, customHeight, skipAuth }, ref) => { - const { rootHeight } = useContext(MyContext); +const AppViewerContainer = React.forwardRef( + ({ app, isSelected, hide, isDevMode, customHeight, skipAuth }, ref) => { + const { rootHeight } = useContext(MyContext); - - return ( - - - - } - style={{ - position: (!isSelected || hide) && 'fixed', - left: (!isSelected || hide) && '-200vw', - height: customHeight ? customHeight : !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px)`, - border: 'none', - width: '100%', - overflow: 'hidden', - }} - > - - - ); -}); + + + } + style={{ + border: 'none', + height: '100vh', + left: (!isSelected || hide) && '-200vw', + overflow: 'hidden', + position: (!isSelected || hide) && 'fixed', + width: '100%', + }} + > + + + ); + } +); export default AppViewerContainer; diff --git a/src/components/Apps/Apps-styles.tsx b/src/components/Apps/Apps-styles.tsx index 18e3af1..a335810 100644 --- a/src/components/Apps/Apps-styles.tsx +++ b/src/components/Apps/Apps-styles.tsx @@ -2,12 +2,12 @@ import { Typography, Box, ButtonBase } from '@mui/material'; import { styled } from '@mui/system'; export const AppsParent = styled(Box)(({ theme }) => ({ + alignItems: 'center', display: 'flex', - width: '100%', flexDirection: 'column', height: '100%', - alignItems: 'center', overflow: 'auto', + width: '100%', // For WebKit-based browsers (Chrome, Safari, etc.) '::-webkit-scrollbar': { width: '0px', // Set the width to 0 to hide the scrollbar @@ -25,34 +25,52 @@ export const AppsParent = styled(Box)(({ theme }) => ({ })); export const AppsContainer = styled(Box)(({ theme }) => ({ - display: 'flex', - width: '90%', - justifyContent: 'space-evenly', - gap: '24px', - flexWrap: 'wrap', alignItems: 'flex-start', alignSelf: 'center', backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, + display: 'flex', + flexWrap: 'wrap', + gap: '24px', + justifyContent: 'space-evenly', + width: '90%', +})); + +export const AppsDesktopLibraryHeader = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + display: 'flex', + flexDirection: 'column', + flexShrink: 0, + width: '100%', +})); + +export const AppsDesktopLibraryBody = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + width: '100%', })); export const AppsLibraryContainer = styled(Box)(({ theme }) => ({ + alignItems: 'center', + backgroundColor: theme.palette.background.default, display: 'flex', - width: '100%', flexDirection: 'column', justifyContent: 'flex-start', - alignItems: 'center', - backgroundColor: theme.palette.background.paper, + width: '100%', })); export const AppsWidthLimiter = styled(Box)(({ theme }) => ({ - display: 'flex', - width: '90%', - flexDirection: 'column', - justifyContent: 'flex-start', alignItems: 'flex-start', backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, + display: 'flex', + flexDirection: 'column', + justifyContent: 'flex-start', + width: '90%', })); export const AppsSearchContainer = styled(Box)(({ theme }) => ({ @@ -99,15 +117,6 @@ export const AppCircleContainer = styled(Box)(({ theme }) => ({ width: '100%', })); -export const Add = styled(Typography)(({ theme }) => ({ - backgroundColor: theme.palette.background.default, - color: theme.palette.text.primary, - fontSize: '36px', - fontWeight: 500, - lineHeight: '43.57px', - textAlign: 'left', -})); - export const AppCircleLabel = styled(Typography)(({ theme }) => ({ backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, @@ -137,9 +146,9 @@ export const AppCircle = styled(Box)(({ theme }) => ({ theme.palette.mode === 'dark' ? 'rgb(209, 209, 209)' : 'rgba(41, 41, 43, 1)', - borderWidth: '1px', borderRadius: '50%', borderStyle: 'solid', + borderWidth: '1px', color: theme.palette.text.primary, display: 'flex', flexDirection: 'column', @@ -167,148 +176,148 @@ export const AppInfoSnippetLeft = styled(Box)(({ theme }) => ({ })); export const AppInfoSnippetRight = styled(Box)(({ theme }) => ({ - display: 'flex', - justifyContent: 'flex-end', alignItems: 'center', backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, + display: 'flex', + justifyContent: 'flex-end', })); export const AppDownloadButton = styled(ButtonBase)(({ theme }) => ({ - backgroundColor: '#247C0E', - color: theme.palette.text.primary, - width: '101px', - height: '29px', - display: 'flex', - justifyContent: 'center', alignItems: 'center', - borderRadius: '25px', alignSelf: 'center', + backgroundColor: theme.palette.background.default, + borderRadius: '25px', + color: theme.palette.text.primary, + display: 'flex', + height: '29px', + justifyContent: 'center', + width: '101px', })); -export const AppDownloadButtonText = styled(Typography)(({ theme }) => ({ +export const AppDownloadButtonText = styled(Typography)({ fontSize: '14px', fontWeight: 500, lineHeight: 1.2, - backgroundColor: theme.palette.background.default, - color: theme.palette.text.primary, -})); +}); export const AppPublishTagsContainer = styled(Box)(({ theme }) => ({ - gap: '10px', - flexWrap: 'wrap', - justifyContent: 'flex-start', - width: '100%', - display: 'flex', backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, + display: 'flex', + flexWrap: 'wrap', + gap: '10px', + justifyContent: 'flex-start', + width: '100%', })); export const AppInfoSnippetMiddle = styled(Box)(({ theme }) => ({ + alignItems: 'flex-start', + backgroundColor: theme.palette.background.default, display: 'flex', flexDirection: 'column', justifyContent: 'center', - alignItems: 'flex-start', - backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, })); export const AppInfoAppName = styled(Typography)(({ theme }) => ({ + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, fontSize: '16px', fontWeight: 500, lineHeight: 1.2, textAlign: 'start', - backgroundColor: theme.palette.background.default, - color: theme.palette.text.primary, })); export const AppInfoUserName = styled(Typography)(({ theme }) => ({ + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, fontSize: '13px', fontWeight: 400, lineHeight: 1.2, textAlign: 'start', - backgroundColor: theme.palette.background.default, - color: theme.palette.text.primary, })); export const AppsNavBarParent = styled(Box)(({ theme }) => ({ - display: 'flex', - justifyContent: 'space-between', alignItems: 'center', - width: '100%', + backgroundColor: theme.palette.background.default, + bottom: 0, + color: theme.palette.text.primary, + display: 'flex', height: '60px', + justifyContent: 'space-between', padding: '0px 10px', position: 'fixed', - bottom: 0, + width: '100%', zIndex: 1, - backgroundColor: theme.palette.background.default, - color: theme.palette.text.primary, })); export const AppsNavBarLeft = styled(Box)(({ theme }) => ({ - display: 'flex', - justifyContent: 'flex-start', alignItems: 'center', - flexGrow: 1, backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, + display: 'flex', + flexGrow: 1, + justifyContent: 'flex-start', })); export const AppsNavBarRight = styled(Box)(({ theme }) => ({ - display: 'flex', - justifyContent: 'flex-end', alignItems: 'center', backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, + display: 'flex', + justifyContent: 'flex-end', })); export const TabParent = styled(Box)(({ theme }) => ({ - height: '36px', - width: '36px', - position: 'relative', - borderRadius: '50%', - display: 'flex', alignItems: 'center', - justifyContent: 'center', backgroundColor: theme.palette.background.default, + borderRadius: '50%', color: theme.palette.text.primary, + display: 'flex', + height: '36px', + justifyContent: 'center', + position: 'relative', + width: '36px', })); export const PublishQAppCTAParent = styled(Box)(({ theme }) => ({ - display: 'flex', - justifyContent: 'space-between', alignItems: 'center', - width: '100%', backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, + display: 'flex', + justifyContent: 'space-between', + width: '100%', })); export const PublishQAppCTALeft = styled(Box)(({ theme }) => ({ - display: 'flex', - justifyContent: 'flex-start', alignItems: 'center', backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, + display: 'flex', + justifyContent: 'flex-start', })); export const PublishQAppCTARight = styled(Box)(({ theme }) => ({ - display: 'flex', - justifyContent: 'flex-end', alignItems: 'center', backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, + display: 'flex', + justifyContent: 'flex-end', })); export const PublishQAppCTAButton = styled(ButtonBase)(({ theme }) => ({ - width: '101px', - height: '29px', - display: 'flex', - justifyContent: 'center', alignItems: 'center', + backgroundColor: theme.palette.background.paper, + borderColor: theme.palette.background.default, borderRadius: '25px', - border: '1px solid #FFFFFF', - backgroundColor: theme.palette.background.default, + borderStyle: 'solid', + borderWidth: '1px', color: theme.palette.text.primary, + display: 'flex', + height: '29px', + justifyContent: 'center', + width: '101px', })); export const PublishQAppDotsBG = styled(Box)(({ theme }) => ({ diff --git a/src/components/Apps/Apps.tsx b/src/components/Apps/Apps.tsx deleted file mode 100644 index b4e6f67..0000000 --- a/src/components/Apps/Apps.tsx +++ /dev/null @@ -1,357 +0,0 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { AppsHome } from './AppsHome'; -import { Spacer } from '../../common/Spacer'; -import { getBaseApiReact } from '../../App'; -import { AppInfo } from './AppInfo'; -import { - executeEvent, - subscribeToEvent, - unsubscribeFromEvent, -} from '../../utils/events'; -import { AppsParent } from './Apps-styles'; -import AppViewerContainer from './AppViewerContainer'; -import ShortUniqueId from 'short-unique-id'; -import { AppPublish } from './AppPublish'; -import { AppsCategory } from './AppsCategory'; -import { AppsLibrary } from './AppsLibrary'; - -const uid = new ShortUniqueId({ length: 8 }); - -export const Apps = ({ mode, setMode, show, myName }) => { - const [availableQapps, setAvailableQapps] = useState([]); - const [selectedAppInfo, setSelectedAppInfo] = useState(null); - const [selectedCategory, setSelectedCategory] = useState(null); - const [tabs, setTabs] = useState([]); - const [selectedTab, setSelectedTab] = useState(null); - const [isNewTabWindow, setIsNewTabWindow] = useState(false); - const [categories, setCategories] = useState([]); - const iframeRefs = useRef({}); - - const myApp = useMemo(() => { - return availableQapps.find( - (app) => app.name === myName && app.service === 'APP' - ); - }, [myName, availableQapps]); - - const myWebsite = useMemo(() => { - return availableQapps.find( - (app) => app.name === myName && app.service === 'WEBSITE' - ); - }, [myName, availableQapps]); - - useEffect(() => { - setTimeout(() => { - executeEvent('setTabsToNav', { - data: { - tabs: tabs, - selectedTab: selectedTab, - isNewTabWindow: isNewTabWindow, - }, - }); - }, 100); - }, [show, tabs, selectedTab, isNewTabWindow]); - - const getCategories = React.useCallback(async () => { - try { - const url = `${getBaseApiReact()}/arbitrary/categories`; - - const response = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - if (!response?.ok) return; - const responseData = await response.json(); - - setCategories(responseData); - } catch (error) { - console.log(error); - } finally { - // dispatch(setIsLoadingGlobal(false)) - } - }, []); - - const getQapps = React.useCallback(async () => { - try { - let apps = []; - let websites = []; - // dispatch(setIsLoadingGlobal(true)) - const url = `${getBaseApiReact()}/arbitrary/resources/search?service=APP&mode=ALL&limit=0&includestatus=true&includemetadata=true`; - - const response = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - if (!response?.ok) return; - const responseData = await response.json(); - const urlWebsites = `${getBaseApiReact()}/arbitrary/resources/search?service=WEBSITE&mode=ALL&limit=0&includestatus=true&includemetadata=true`; - - const responseWebsites = await fetch(urlWebsites, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - if (!responseWebsites?.ok) return; - const responseDataWebsites = await responseWebsites.json(); - - apps = responseData; - websites = responseDataWebsites; - const combine = [...apps, ...websites]; - setAvailableQapps(combine); - } catch (error) { - console.log(error); - } finally { - // dispatch(setIsLoadingGlobal(false)) - } - }, []); - useEffect(() => { - getQapps(); - getCategories(); - }, [getQapps, getCategories]); - - const selectedAppInfoFunc = (e) => { - const data = e.detail?.data; - setSelectedAppInfo(data); - setMode('appInfo'); - }; - - useEffect(() => { - subscribeToEvent('selectedAppInfo', selectedAppInfoFunc); - - return () => { - unsubscribeFromEvent('selectedAppInfo', selectedAppInfoFunc); - }; - }, []); - - const selectedAppInfoCategoryFunc = (e) => { - const data = e.detail?.data; - setSelectedAppInfo(data); - setMode('appInfo-from-category'); - }; - - useEffect(() => { - subscribeToEvent('selectedAppInfoCategory', selectedAppInfoCategoryFunc); - - return () => { - unsubscribeFromEvent( - 'selectedAppInfoCategory', - selectedAppInfoCategoryFunc - ); - }; - }, []); - - const selectedCategoryFunc = (e) => { - const data = e.detail?.data; - setSelectedCategory(data); - setMode('category'); - }; - - useEffect(() => { - subscribeToEvent('selectedCategory', selectedCategoryFunc); - - return () => { - unsubscribeFromEvent('selectedCategory', selectedCategoryFunc); - }; - }, []); - - const navigateBackFunc = (e) => { - if ( - [ - 'category', - 'appInfo-from-category', - 'appInfo', - 'library', - 'publish', - ].includes(mode) - ) { - // Handle the various modes as needed - if (mode === 'category') { - setMode('library'); - setSelectedCategory(null); - } else if (mode === 'appInfo-from-category') { - setMode('category'); - } else if (mode === 'appInfo') { - setMode('library'); - } else if (mode === 'library') { - if (isNewTabWindow) { - setMode('viewer'); - } else { - setMode('home'); - } - } else if (mode === 'publish') { - setMode('library'); - } - } else if (selectedTab?.tabId) { - executeEvent(`navigateBackApp-${selectedTab?.tabId}`, {}); - } - }; - - useEffect(() => { - subscribeToEvent('navigateBack', navigateBackFunc); - - return () => { - unsubscribeFromEvent('navigateBack', navigateBackFunc); - }; - }, [mode, selectedTab]); - - const addTabFunc = (e) => { - const data = e.detail?.data; - const newTab = { - ...data, - tabId: uid.rnd(), - }; - setTabs((prev) => [...prev, newTab]); - setSelectedTab(newTab); - setMode('viewer'); - - setIsNewTabWindow(false); - }; - - useEffect(() => { - subscribeToEvent('addTab', addTabFunc); - - return () => { - unsubscribeFromEvent('addTab', addTabFunc); - }; - }, [tabs]); - - const setSelectedTabFunc = (e) => { - const data = e.detail?.data; - - setSelectedTab(data); - setTimeout(() => { - executeEvent('setTabsToNav', { - data: { - tabs: tabs, - selectedTab: data, - isNewTabWindow: isNewTabWindow, - }, - }); - }, 100); - setIsNewTabWindow(false); - }; - - useEffect(() => { - subscribeToEvent('setSelectedTab', setSelectedTabFunc); - - return () => { - unsubscribeFromEvent('setSelectedTab', setSelectedTabFunc); - }; - }, [tabs, isNewTabWindow]); - - const removeTabFunc = (e) => { - const data = e.detail?.data; - const copyTabs = [...tabs].filter((tab) => tab?.tabId !== data?.tabId); - if (copyTabs?.length === 0) { - setMode('home'); - } else { - setSelectedTab(copyTabs[0]); - } - setTabs(copyTabs); - setSelectedTab(copyTabs[0]); - setTimeout(() => { - executeEvent('setTabsToNav', { - data: { - tabs: copyTabs, - selectedTab: copyTabs[0], - }, - }); - }, 400); - }; - - useEffect(() => { - subscribeToEvent('removeTab', removeTabFunc); - - return () => { - unsubscribeFromEvent('removeTab', removeTabFunc); - }; - }, [tabs]); - - const setNewTabWindowFunc = (e) => { - setIsNewTabWindow(true); - setSelectedTab(null); - }; - - useEffect(() => { - subscribeToEvent('newTabWindow', setNewTabWindowFunc); - - return () => { - unsubscribeFromEvent('newTabWindow', setNewTabWindowFunc); - }; - }, [tabs]); - - return ( - - {mode !== 'viewer' && !selectedTab && } - {mode === 'home' && ( - - )} - - - - {mode === 'appInfo' && !selectedTab && ( - - )} - {mode === 'appInfo-from-category' && !selectedTab && ( - - )} - - {mode === 'publish' && !selectedTab && ( - - )} - - {tabs.map((tab) => { - if (!iframeRefs.current[tab.tabId]) { - iframeRefs.current[tab.tabId] = React.createRef(); - } - return ( - - ); - })} - - {isNewTabWindow && mode === 'viewer' && ( - <> - - - - )} - {mode !== 'viewer' && !selectedTab && } - - ); -}; diff --git a/src/components/Apps/AppsCategory.tsx b/src/components/Apps/AppsCategory.tsx deleted file mode 100644 index 4871026..0000000 --- a/src/components/Apps/AppsCategory.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; -import { - AppCircle, - AppCircleContainer, - AppCircleLabel, - AppLibrarySubTitle, - AppsContainer, - AppsLibraryContainer, - AppsParent, - AppsSearchContainer, - AppsSearchLeft, - AppsSearchRight, - AppsWidthLimiter, - PublishQAppCTAButton, - PublishQAppCTALeft, - PublishQAppCTAParent, - PublishQAppCTARight, - PublishQAppDotsBG, -} from "./Apps-styles"; -import { Avatar, Box, ButtonBase, InputBase, styled } from "@mui/material"; -import { Add } from "@mui/icons-material"; -import { MyContext, getBaseApiReact } from "../../App"; -import LogoSelected from "../../assets/svgs/LogoSelected.svg"; -import IconSearch from "../../assets/svgs/Search.svg"; -import IconClearInput from "../../assets/svgs/ClearInput.svg"; -import qappDevelopText from "../../assets/svgs/qappDevelopText.svg"; -import qappDots from "../../assets/svgs/qappDots.svg"; - -import { Spacer } from "../../common/Spacer"; -import { AppInfoSnippet } from "./AppInfoSnippet"; -import { Virtuoso } from "react-virtuoso"; -import { executeEvent } from "../../utils/events"; -const officialAppList = [ - "q-tube", - "q-blog", - "q-share", - "q-support", - "q-mail", - "q-fund", - "q-shop", - "q-trade", - "q-support", - "q-manager", - "q-wallets", - "q-search", - "q-nodecontrol" -]; - -const ScrollerStyled = styled('div')({ - // Hide scrollbar for WebKit browsers (Chrome, Safari) - "::-webkit-scrollbar": { - width: "0px", - height: "0px", - }, - - // Hide scrollbar for Firefox - scrollbarWidth: "none", - - // Hide scrollbar for IE and older Edge - "-msOverflowStyle": "none", - }); - - const StyledVirtuosoContainer = styled('div')({ - position: 'relative', - width: '100%', - display: 'flex', - flexDirection: 'column', - - // Hide scrollbar for WebKit browsers (Chrome, Safari) - "::-webkit-scrollbar": { - width: "0px", - height: "0px", - }, - - // Hide scrollbar for Firefox - scrollbarWidth: "none", - - // Hide scrollbar for IE and older Edge - "-msOverflowStyle": "none", - }); - -export const AppsCategory = ({ availableQapps, myName, category, isShow }) => { - const [searchValue, setSearchValue] = useState(""); - const virtuosoRef = useRef(); - const { rootHeight } = useContext(MyContext); - - - - const categoryList = useMemo(() => { - return availableQapps.filter( - (app) => - app?.metadata?.category === category?.id - ); - }, [availableQapps, category]); - - const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value - - // Debounce logic - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedValue(searchValue); - }, 350); - - // Cleanup timeout if searchValue changes before the timeout completes - return () => { - clearTimeout(handler); - }; - }, [searchValue]); // Runs effect when searchValue changes - - // Example: Perform search or other actions based on debouncedValue - - const searchedList = useMemo(() => { - if (!debouncedValue) return categoryList - return categoryList.filter((app) => - app.name.toLowerCase().includes(debouncedValue.toLowerCase()) - ); - }, [debouncedValue, categoryList]); - - const rowRenderer = (index) => { - - let app = searchedList[index]; - return ; - }; - - - - return ( - - - - - - - setSearchValue(e.target.value)} - sx={{ ml: 1, flex: 1 }} - placeholder="Search for apps" - inputProps={{ - "aria-label": "Search for apps", - fontSize: "16px", - fontWeight: 400, - }} - /> - - - {searchValue && ( - { - setSearchValue(""); - }} - > - - - )} - - - - - - - {`Category: ${category?.name}`} - - - - - - - - - - - ); -}; diff --git a/src/components/Apps/AppsCategoryDesktop.tsx b/src/components/Apps/AppsCategoryDesktop.tsx index fbad9a7..5103488 100644 --- a/src/components/Apps/AppsCategoryDesktop.tsx +++ b/src/components/Apps/AppsCategoryDesktop.tsx @@ -1,61 +1,21 @@ -import React, { - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { - AppCircle, - AppCircleContainer, - AppCircleLabel, AppLibrarySubTitle, - AppsContainer, + AppsDesktopLibraryBody, + AppsDesktopLibraryHeader, AppsLibraryContainer, - AppsParent, AppsSearchContainer, AppsSearchLeft, AppsSearchRight, AppsWidthLimiter, - PublishQAppCTAButton, - PublishQAppCTALeft, - PublishQAppCTAParent, - PublishQAppCTARight, - PublishQAppDotsBG, } from './Apps-styles'; -import { Avatar, Box, ButtonBase, InputBase, styled } from '@mui/material'; -import { Add } from '@mui/icons-material'; -import { MyContext, getBaseApiReact } from '../../App'; -import LogoSelected from '../../assets/svgs/LogoSelected.svg'; -import IconSearch from '../../assets/svgs/Search.svg'; +import { ButtonBase, InputBase, styled, useTheme } from '@mui/material'; +import { MyContext } from '../../App'; +import SearchIcon from '@mui/icons-material/Search'; import IconClearInput from '../../assets/svgs/ClearInput.svg'; -import qappDevelopText from '../../assets/svgs/qappDevelopText.svg'; -import qappDots from '../../assets/svgs/qappDots.svg'; - import { Spacer } from '../../common/Spacer'; import { AppInfoSnippet } from './AppInfoSnippet'; import { Virtuoso } from 'react-virtuoso'; -import { executeEvent } from '../../utils/events'; -import { - AppsDesktopLibraryBody, - AppsDesktopLibraryHeader, -} from './AppsDesktop-styles'; -const officialAppList = [ - 'q-tube', - 'q-blog', - 'q-share', - 'q-support', - 'q-mail', - 'q-fund', - 'q-shop', - 'q-trade', - 'q-support', - 'q-manager', - 'q-wallets', - 'q-search', - 'q-nodecontrol', -]; const ScrollerStyled = styled('div')({ // Hide scrollbar for WebKit browsers (Chrome, Safari) @@ -68,7 +28,7 @@ const ScrollerStyled = styled('div')({ scrollbarWidth: 'none', // Hide scrollbar for IE and older Edge - '-msOverflowStyle': 'none', + msOverflowStyle: 'none', }); const StyledVirtuosoContainer = styled('div')({ @@ -87,7 +47,7 @@ const StyledVirtuosoContainer = styled('div')({ scrollbarWidth: 'none', // Hide scrollbar for IE and older Edge - '-msOverflowStyle': 'none', + msOverflowStyle: 'none', }); export const AppsCategoryDesktop = ({ @@ -98,6 +58,7 @@ export const AppsCategoryDesktop = ({ }) => { const [searchValue, setSearchValue] = useState(''); const virtuosoRef = useRef(); + const theme = useTheme(); const { rootHeight } = useContext(MyContext); const categoryList = useMemo(() => { @@ -158,15 +119,15 @@ export const AppsCategoryDesktop = ({ @@ -181,11 +142,18 @@ export const AppsCategoryDesktop = ({ }} > - + + setSearchValue(e.target.value)} - sx={{ ml: 1, flex: 1 }} + sx={{ + background: theme.palette.background.paper, + borderRadius: '6px', + flex: 1, + ml: 1, + paddingLeft: '12px', + }} placeholder="Search for apps" inputProps={{ 'aria-label': 'Search for apps', @@ -194,6 +162,7 @@ export const AppsCategoryDesktop = ({ }} /> + {searchValue && ( + + {`Category: ${category?.name}`} + ({ - display: "flex", - flexDirection: 'column', - flexShrink: 0, - width: '100%' - })); - export const AppsDesktopLibraryBody = styled(Box)(({ theme }) => ({ - display: "flex", - flexDirection: 'column', - flexGrow: 1, - width: '100%' - })); \ No newline at end of file diff --git a/src/components/Apps/AppsDesktop.tsx b/src/components/Apps/AppsDesktop.tsx index 6952be4..c7bb5c6 100644 --- a/src/components/Apps/AppsDesktop.tsx +++ b/src/components/Apps/AppsDesktop.tsx @@ -478,13 +478,13 @@ export const AppsDesktop = ({ )} {mode === 'appInfo' && !selectedTab && ( diff --git a/src/components/Apps/AppsDevModeHome.tsx b/src/components/Apps/AppsDevModeHome.tsx index 8c346c2..0fc8242 100644 --- a/src/components/Apps/AppsDevModeHome.tsx +++ b/src/components/Apps/AppsDevModeHome.tsx @@ -22,7 +22,7 @@ import { Input, } from '@mui/material'; import { Add } from '@mui/icons-material'; -import { MyContext, getBaseApiReact, isMobile } from '../../App'; +import { MyContext, getBaseApiReact } from '../../App'; import LogoSelected from '../../assets/svgs/LogoSelected.svg'; import { executeEvent } from '../../utils/events'; import { Spacer } from '../../common/Spacer'; @@ -279,7 +279,9 @@ export const AppsDevModeHome = ({ Dev Mode Apps + + @@ -302,6 +304,7 @@ export const AppsDevModeHome = ({ Server + { addPreviewApp(); @@ -309,15 +312,17 @@ export const AppsDevModeHome = ({ > + + Zip + { addPreviewAppWithDirectory(); @@ -325,7 +330,7 @@ export const AppsDevModeHome = ({ > @@ -334,6 +339,7 @@ export const AppsDevModeHome = ({ Directory + { executeEvent('appsDevModeAddTab', { @@ -347,7 +353,7 @@ export const AppsDevModeHome = ({ > @@ -371,9 +377,11 @@ export const AppsDevModeHome = ({ /> + Q-Sandbox + { executeEvent('appsDevModeAddTab', { @@ -387,7 +395,7 @@ export const AppsDevModeHome = ({ > @@ -411,10 +419,12 @@ export const AppsDevModeHome = ({ /> + API + {isShow && ( {'Add custom framework'} + + - + + + {combinedListTempAndReal.map((message, index, list) => { let fullMessage = message; @@ -780,17 +759,17 @@ export const Thread = ({ > @@ -812,6 +791,7 @@ export const Thread = ({ {message?.name?.charAt(0)} + + {formatTimestampForum(message?.created)} + @@ -908,6 +890,7 @@ export const Thread = ({ {message?.name?.charAt(0)} + + {formatTimestampForum(message?.created)} + @@ -952,9 +937,9 @@ export const Thread = ({ + {loading && promotions.length === 0 && ( @@ -589,6 +583,7 @@ export const ListOfGroupPromotions = () => { > Group name: {` ${promotion?.groupName}`} + { Number of members:{' '} {` ${promotion?.memberCount}`} + {promotion?.description && ( { {promotion?.description} )} + {promotion?.isOpen === false && ( { your request )} + + { > Close + { > {promotion?.name?.charAt(0)} + { {promotion?.name} + { {promotion?.groupName} + + { : 'Private group'} + + { > {promotion?.data} + + { + @@ -779,6 +788,7 @@ export const ListOfGroupPromotions = () => { + {isShowModal && ( diff --git a/src/components/Group/ListOfThreadPostsWatched.tsx b/src/components/Group/ListOfThreadPostsWatched.tsx index 6e24ba3..e0a9d00 100644 --- a/src/components/Group/ListOfThreadPostsWatched.tsx +++ b/src/components/Group/ListOfThreadPostsWatched.tsx @@ -1,21 +1,14 @@ -import * as React from "react"; -import List from "@mui/material/List"; -import ListItem from "@mui/material/ListItem"; -import ListItemButton from "@mui/material/ListItemButton"; -import ListItemIcon from "@mui/material/ListItemIcon"; -import ListItemText from "@mui/material/ListItemText"; -import Checkbox from "@mui/material/Checkbox"; -import IconButton from "@mui/material/IconButton"; -import CommentIcon from "@mui/icons-material/Comment"; -import InfoIcon from "@mui/icons-material/Info"; -import GroupAddIcon from "@mui/icons-material/GroupAdd"; -import { executeEvent } from "../../utils/events"; -import { Box, Typography } from "@mui/material"; -import { Spacer } from "../../common/Spacer"; -import { getGroupNames } from "./UserListOfInvites"; -import { CustomLoader } from "../../common/CustomLoader"; -import VisibilityIcon from "@mui/icons-material/Visibility"; -import { isMobile } from "../../App"; +import * as React from 'react'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import IconButton from '@mui/material/IconButton'; +import { executeEvent } from '../../utils/events'; +import { Box, Typography } from '@mui/material'; +import { Spacer } from '../../common/Spacer'; +import { CustomLoader } from '../../common/CustomLoader'; +import VisibilityIcon from '@mui/icons-material/Visibility'; export const ListOfThreadPostsWatched = () => { const [posts, setPosts] = React.useState([]); @@ -24,33 +17,33 @@ export const ListOfThreadPostsWatched = () => { const getPosts = async () => { try { await new Promise((res, rej) => { - window.sendMessage("getThreadActivity", {}) - .then((response) => { - if (!response?.error) { - if (!response) { - res(null); + window + .sendMessage('getThreadActivity', {}) + .then((response) => { + if (!response?.error) { + if (!response) { + res(null); + return; + } + const uniquePosts = response.reduce((acc, current) => { + const x = acc.find( + (item) => item?.thread?.threadId === current?.thread?.threadId + ); + if (!x) { + return acc.concat([current]); + } else { + return acc; + } + }, []); + setPosts(uniquePosts); + res(uniquePosts); return; } - const uniquePosts = response.reduce((acc, current) => { - const x = acc.find( - (item) => item?.thread?.threadId === current?.thread?.threadId - ); - if (!x) { - return acc.concat([current]); - } else { - return acc; - } - }, []); - setPosts(uniquePosts); - res(uniquePosts); - return; - } - rej(response.error); - }) - .catch((error) => { - rej(error.message || "An error occurred"); - }); - + rej(response.error); + }) + .catch((error) => { + rej(error.message || 'An error occurred'); + }); }); } catch (error) { } finally { @@ -63,49 +56,50 @@ export const ListOfThreadPostsWatched = () => { }, []); return ( - + New Thread Posts: - + {loading && posts.length === 0 && ( @@ -114,19 +108,18 @@ export const ListOfThreadPostsWatched = () => { {!loading && posts.length === 0 && ( Nothing to display @@ -134,47 +127,46 @@ export const ListOfThreadPostsWatched = () => { )} {posts?.length > 0 && ( - - {posts?.map((post) => { - return ( - { - executeEvent("openThreadNewPost", { - data: post, - }); - }} - disablePadding - secondaryAction={ - - - - } - > - - - - - ); - })} - + + {posts?.map((post) => { + return ( + { + executeEvent('openThreadNewPost', { + data: post, + }); + }} + disablePadding + secondaryAction={ + + + + } + > + + + + + ); + })} + )} - ); diff --git a/src/components/Group/ManageMembers.tsx b/src/components/Group/ManageMembers.tsx index de361fa..a69dde4 100644 --- a/src/components/Group/ManageMembers.tsx +++ b/src/components/Group/ManageMembers.tsx @@ -1,10 +1,6 @@ import * as React from 'react'; import Button from '@mui/material/Button'; import Dialog from '@mui/material/Dialog'; -import ListItemText from '@mui/material/ListItemText'; -import ListItemButton from '@mui/material/ListItemButton'; -import List from '@mui/material/List'; -import Divider from '@mui/material/Divider'; import AppBar from '@mui/material/AppBar'; import Toolbar from '@mui/material/Toolbar'; import IconButton from '@mui/material/IconButton'; @@ -19,7 +15,7 @@ import { ListOfBans } from './ListOfBans'; import { ListOfJoinRequests } from './ListOfJoinRequests'; import { Box, ButtonBase, Card, Tab, Tabs } from '@mui/material'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; -import { MyContext, getBaseApiReact, isMobile } from '../../App'; +import { MyContext, getBaseApiReact } from '../../App'; import { getGroupMembers, getNames } from './Group'; import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; import { getFee } from '../../background'; @@ -27,6 +23,7 @@ import { LoadingButton } from '@mui/lab'; import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; import { Spacer } from '../../common/Spacer'; import InsertLinkIcon from '@mui/icons-material/InsertLink'; + function a11yProps(index: number) { return { id: `simple-tab-${index}`, @@ -193,9 +190,9 @@ export const ManageMembers = ({ @@ -221,9 +218,10 @@ export const ManageMembers = ({ '&.Mui-selected': { color: 'white', }, - fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile + fontSize: '1rem', }} /> + + + + + GroupId: {groupInfo?.groupId} + GroupName: {groupInfo?.groupName} + Number of members: {groupInfo?.memberCount} + Join Group Link + + {selectedGroup?.groupId && !isOwner && ( Load members with names + + )} + + { borderRadius: '19px', display: 'flex', flexDirection: 'column', - height: isMobile ? '165px' : '250px', + height: '250px', overflow: 'auto', padding: '20px', width: '322px', diff --git a/src/components/Group/ThingsToDoInitial.tsx b/src/components/Group/ThingsToDoInitial.tsx index 367b3b1..023cfde 100644 --- a/src/components/Group/ThingsToDoInitial.tsx +++ b/src/components/Group/ThingsToDoInitial.tsx @@ -1,28 +1,23 @@ -import * as React from "react"; -import List from "@mui/material/List"; -import ListItem from "@mui/material/ListItem"; -import ListItemButton from "@mui/material/ListItemButton"; -import ListItemIcon from "@mui/material/ListItemIcon"; -import ListItemText from "@mui/material/ListItemText"; -import Checkbox from "@mui/material/Checkbox"; -import IconButton from "@mui/material/IconButton"; -import CommentIcon from "@mui/icons-material/Comment"; -import InfoIcon from "@mui/icons-material/Info"; -import { Box, Typography } from "@mui/material"; -import { Spacer } from "../../common/Spacer"; -import { isMobile } from "../../App"; -import { QMailMessages } from "./QMailMessages"; -import { executeEvent } from "../../utils/events"; +import * as React from 'react'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import { Box, Typography } from '@mui/material'; +import { Spacer } from '../../common/Spacer'; +import { QMailMessages } from './QMailMessages'; +import { executeEvent } from '../../utils/events'; -export const ThingsToDoInitial = ({ myAddress, name, hasGroups, balance, userInfo }) => { +export const ThingsToDoInitial = ({ + myAddress, + name, + hasGroups, + balance, + userInfo, +}) => { const [checked1, setChecked1] = React.useState(false); const [checked2, setChecked2] = React.useState(false); - // const [checked3, setChecked3] = React.useState(false); - - // React.useEffect(() => { - // if (hasGroups) setChecked3(true); - // }, [hasGroups]); - React.useEffect(() => { if (balance && +balance >= 6) { @@ -30,111 +25,114 @@ export const ThingsToDoInitial = ({ myAddress, name, hasGroups, balance, userInf } }, [balance]); - React.useEffect(() => { if (name) setChecked2(true); }, [name]); + const isLoaded = React.useMemo(() => { + if (userInfo !== null) return true; + return false; + }, [userInfo]); - const isLoaded = React.useMemo(()=> { - if(userInfo !== null) return true - return false - }, [ userInfo]) + const hasDoneNameAndBalanceAndIsLoaded = React.useMemo(() => { + if (isLoaded && checked1 && checked2) return true; + return false; + }, [checked1, isLoaded, checked2]); - const hasDoneNameAndBalanceAndIsLoaded = React.useMemo(()=> { - if(isLoaded && checked1 && checked2) return true - return false -}, [checked1, isLoaded, checked2]) - -if(hasDoneNameAndBalanceAndIsLoaded){ - return ( - - ); -} -if(!isLoaded) return null + if (hasDoneNameAndBalanceAndIsLoaded) { + return ( + + ); + } + if (!isLoaded) return null; return ( - {!isLoaded ? 'Loading...' : 'Getting Started' } + {!isLoaded ? 'Loading...' : 'Getting Started'} + {isLoaded && ( - - - { - executeEvent("openBuyQortInfo", {}) - }} - > - - - - {/* + + { + executeEvent('openBuyQortInfo', {}); + }} + > + + + + {/* */} - - - - - // - // - // } - disablePadding - > - - - { - executeEvent('openRegisterName', {}) - }} sx={{ - "& .MuiTypography-root": { - fontSize: "1rem", - fontWeight: 400, - }, - }} primary={`Register a name`} /> - - - - - - {/* + + + + // + // + // } + disablePadding + > + + { + executeEvent('openRegisterName', {}); + }} + sx={{ + '& .MuiTypography-root': { + fontSize: '1rem', + fontWeight: 400, + }, + }} + primary={`Register a name`} + /> + + + + + + {/* */} - + )} - ); diff --git a/src/components/Mobile/MobileFooter.tsx b/src/components/Mobile/MobileFooter.tsx deleted file mode 100644 index 52e5c7c..0000000 --- a/src/components/Mobile/MobileFooter.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import * as React from "react"; -import { - BottomNavigation, - BottomNavigationAction, - ButtonBase, - Typography, -} from "@mui/material"; -import { Home, Groups, Message, ShowChart } from "@mui/icons-material"; -import Box from "@mui/material/Box"; -import BottomLogo from "../../assets/svgs/BottomLogo5.svg"; -import LogoSelected from "../../assets/svgs/LogoSelected.svg"; -import { Browser } from '@capacitor/browser'; - -import { CustomSvg } from "../../common/CustomSvg"; -import { WalletIcon } from "../../assets/Icons/WalletIcon"; -import { HubsIcon } from "../../assets/Icons/HubsIcon"; -import { TradingIcon } from "../../assets/Icons/TradingIcon"; -import { MessagingIcon } from "../../assets/Icons/MessagingIcon"; -import { executeEvent } from "../../utils/events"; - -const IconWrapper = ({ children, label, color }) => { - return ( - - {children} - - {label} - - - ); -}; - -export const MobileFooter = ({ - selectedGroup, - groupSection, - isUnread, - goToAnnouncements, - isUnreadChat, - goToChat, - goToThreads, - setOpenManageMembers, - groupChatHasUnread, - groupsAnnHasUnread, - directChatHasUnread, - chatMode, - openDrawerGroups, - goToHome, - setIsOpenDrawerProfile, - mobileViewMode, - setMobileViewMode, - setMobileViewModeKeepOpen, - hasUnreadGroups, - hasUnreadDirects -}) => { - const [value, setValue] = React.useState(0); - return ( - - setValue(newValue)} - sx={{ backgroundColor: "transparent", flexGrow: 1 }} - > - { - // setMobileViewMode('wallet') - setIsOpenDrawerProfile(true); - }} - icon={ - - - - } - sx={{ color: value === 0 ? "white" : "gray", padding: "0px 10px" }} - /> - { - setMobileViewMode("groups"); - }} - icon={ - - - - } - sx={{ - color: value === 0 ? "white" : "gray", - paddingLeft: "10px", - paddingRight: "42px", - }} - /> - - - {/* Floating Center Button */} - - { - if(mobileViewMode === 'home'){ - setMobileViewMode('apps') - - } else { - setMobileViewMode('home') - - } - }}> - - {/* Custom Center Icon */} - center-icon - - - - - setValue(newValue)} - sx={{ backgroundColor: "transparent", flexGrow: 1 }} - > - { - setMobileViewModeKeepOpen("messaging"); - }} - icon={ - - - - } - sx={{ - color: value === 2 ? "white" : "gray", - paddingLeft: "55px", - paddingRight: "10px", - }} - /> - { - executeEvent("addTab", { data: { service: 'APP', name: 'q-trade' } }); - executeEvent("open-apps-mode", { }); - }} - - icon={ - - - - } - sx={{ color: value === 3 ? "white" : "gray", padding: "0px 10px" }} - /> - - - ); -}; diff --git a/src/components/Mobile/MobileHeader.tsx b/src/components/Mobile/MobileHeader.tsx deleted file mode 100644 index 1ab74dc..0000000 --- a/src/components/Mobile/MobileHeader.tsx +++ /dev/null @@ -1,528 +0,0 @@ -import React, { useState } from "react"; -import { - AppBar, - Toolbar, - IconButton, - Typography, - Box, - MenuItem, - Select, - ButtonBase, - Menu, - ListItemIcon, - ListItemText, -} from "@mui/material"; -import { HomeIcon } from "../../assets/Icons/HomeIcon"; -import { LogoutIcon } from "../../assets/Icons/LogoutIcon"; -import { NotificationIcon } from "../../assets/Icons/NotificationIcon"; -import { ArrowDownIcon } from "../../assets/Icons/ArrowDownIcon"; -import { MessagingIcon } from "../../assets/Icons/MessagingIcon"; -import { MessagingIcon2 } from "../../assets/Icons/MessagingIcon2"; -import { HubsIcon } from "../../assets/Icons/HubsIcon"; -import { Save } from "../Save/Save"; -import CloseFullscreenIcon from "@mui/icons-material/CloseFullscreen"; -import { useRecoilState } from "recoil"; -import { fullScreenAtom, hasSettingsChangedAtom } from "../../atoms/global"; -import { useAppFullScreen } from "../../useAppFullscreen"; - -const Header = ({ - logoutFunc, - goToHome, - setIsOpenDrawerProfile, - isThin, - setMobileViewModeKeepOpen, - hasUnreadGroups, - hasUnreadDirects, - setMobileViewMode, - myName, - setSelectedDirect, - setNewChat, -}) => { - const [anchorEl, setAnchorEl] = useState(null); - const open = Boolean(anchorEl); - const [fullScreen, setFullScreen] = useRecoilState(fullScreenAtom); - const { exitFullScreen } = useAppFullScreen(setFullScreen); - const handleClick = (event) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - if (isThin) { - return ( - - - {/* Left Home Icon */} - - { - setMobileViewModeKeepOpen(""); - goToHome(); - }} - // onClick={onHomeClick} - > - - - - - - {fullScreen && ( - { - exitFullScreen(); - setFullScreen(false); - }} - > - - - )} - - - {/* Center Title */} - - QORTAL - - - {/* Right Logout Icon */} - - { - setMobileViewModeKeepOpen("messaging"); - }} - > - - - - - - - - - - { - setSelectedDirect(null); - setNewChat(false); - setMobileViewMode("groups"); - setMobileViewModeKeepOpen(""); - handleClose(); - }} - > - - - - - - { - setMobileViewModeKeepOpen("messaging"); - - handleClose(); - }} - > - - - - - - - - ); - } - return ( - <> - {/* Main Header */} - - - {/* Left Home Icon */} - - - - - {fullScreen && ( - { - exitFullScreen(); - setFullScreen(false); - }} - > - - - )} - - {/* Center Title */} - - QORTAL - - - {/* Right Logout Icon */} - - - - - - - - - {/* Secondary Section */} - - - - {myName} - - {/* - */} - - - - - - - - - {/* Right Dropdown */} - {/* { - setIsOpenDrawerProfile(true); - }} - > - - - View Wallet - - - - - */} - - - - { - setMobileViewMode("groups"); - setMobileViewModeKeepOpen(""); - handleClose(); - }} - > - - - - - - - { - setMobileViewModeKeepOpen("messaging"); - - handleClose(); - }} - > - - - - - - - - ); -}; - -export default Header; diff --git a/src/components/PasswordField/PasswordField.tsx b/src/components/PasswordField/PasswordField.tsx index 31f0a49..b59538e 100644 --- a/src/components/PasswordField/PasswordField.tsx +++ b/src/components/PasswordField/PasswordField.tsx @@ -4,7 +4,6 @@ import { TextField, TextFieldProps, styled, - useTheme, } from '@mui/material'; import { forwardRef, useState } from 'react'; import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; @@ -52,7 +51,6 @@ export const CustomInput = styled(TextField)(({ theme }) => ({ export const PasswordField = forwardRef( ({ ...props }, ref) => { const [canViewPassword, setCanViewPassword] = useState(false); - const theme = useTheme(); return ( { const [showPicker, setShowPicker] = useState(false); @@ -30,7 +29,7 @@ export const ReactionPicker = ({ onReaction }) => { } else { // Get the button's position const buttonRect = buttonRef.current.getBoundingClientRect(); - const pickerWidth = isMobile ? 300 : 350; // Adjust based on picker width + const pickerWidth = 350; // Calculate position to align the right edge of the picker with the button's right edge setPickerPosition({ @@ -90,15 +89,15 @@ export const ReactionPicker = ({ onReaction }) => { }} > , document.body diff --git a/src/components/Save/Save.tsx b/src/components/Save/Save.tsx index cf8dba6..c8196d8 100644 --- a/src/components/Save/Save.tsx +++ b/src/components/Save/Save.tsx @@ -19,7 +19,7 @@ import { SaveIcon } from '../../assets/Icons/SaveIcon'; import { IconWrapper } from '../Desktop/DesktopFooter'; import { Spacer } from '../../common/Spacer'; import { LoadingButton } from '@mui/lab'; -import { saveToLocalStorage } from '../Apps/AppsNavBar'; +import { saveToLocalStorage } from '../Apps/AppsNavBarDesktop'; import { decryptData, encryptData } from '../../qortalRequests/get'; import { saveFileToDiskGeneric } from '../../utils/generateWallet/generateWallet'; import { diff --git a/src/components/TaskManager/TaskManager.tsx b/src/components/TaskManager/TaskManager.tsx index f1f075b..673c385 100644 --- a/src/components/TaskManager/TaskManager.tsx +++ b/src/components/TaskManager/TaskManager.tsx @@ -12,7 +12,7 @@ import PendingIcon from '@mui/icons-material/Pending'; import TaskAltIcon from '@mui/icons-material/TaskAlt'; import ExpandLess from '@mui/icons-material/ExpandLess'; import ExpandMore from '@mui/icons-material/ExpandMore'; -import { MyContext, getBaseApiReact, isMobile } from '../../App'; +import { MyContext, getBaseApiReact } from '../../App'; import { executeEvent } from '../../utils/events'; export const TaskManager = ({ getUserInfo }) => { @@ -141,8 +141,7 @@ export const TaskManager = ({ getUserInfo }) => { }); }, [txList]); - if (isMobile || txList?.length === 0 || txList.every((item) => item?.done)) - return null; + if (txList?.length === 0 || txList.every((item) => item?.done)) return null; return ( <> diff --git a/src/components/Tutorials/Tutorials.tsx b/src/components/Tutorials/Tutorials.tsx index 4f9ea49..382774c 100644 --- a/src/components/Tutorials/Tutorials.tsx +++ b/src/components/Tutorials/Tutorials.tsx @@ -1,72 +1,103 @@ -import React, { useContext, useState } from 'react' -import { GlobalContext, MyContext } from '../../App'; -import { Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Tab, Tabs, Typography } from '@mui/material'; +import { useContext, useState } from 'react'; +import { GlobalContext } from '../../App'; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + Tab, + Tabs, + useTheme, +} from '@mui/material'; import CloseIcon from '@mui/icons-material/Close'; import { VideoPlayer } from '../Embeds/VideoPlayer'; export const Tutorials = () => { - const { openTutorialModal, setOpenTutorialModal } = useContext(GlobalContext); - const [multiNumber, setMultiNumber] = useState(0) - const handleClose = ()=> { - setOpenTutorialModal(null) - setMultiNumber(0) - } - if(!openTutorialModal) return null - if(openTutorialModal?.multi){ - const selectedTutorial = openTutorialModal?.multi[multiNumber] - return ( - { + setOpenTutorialModal(null); + setMultiNumber(0); + }; + + if (!openTutorialModal) return null; + + if (openTutorialModal?.multi) { + const selectedTutorial = openTutorialModal?.multi[multiNumber]; + return ( + - setMultiNumber(value)} aria-label="basic tabs example"> - {openTutorialModal?.multi?.map((item, index)=> { - return ( - - - ) - })} - - + setMultiNumber(value)} + aria-label="basic tabs example" + > + {openTutorialModal?.multi?.map((item, index) => { + return ( + + ); + })} + + + {selectedTutorial?.title} {` Tutorial`} + ({ + sx={{ position: 'absolute', right: 8, top: 8, - color: theme.palette.grey[500], - })} + color: theme.palette.text.primary, + }} > - - + + - ) - } + ); + } + return ( <> { fullWidth={true} maxWidth="xl" > - + {openTutorialModal?.title} {` Tutorial`} + ({ + sx={{ position: 'absolute', right: 8, top: 8, - color: theme.palette.grey[500], - })} + color: theme.palette.text.primary, + }} > - - + + + - ) -} + ); +}; diff --git a/src/components/Tutorials/useHandleTutorials.tsx b/src/components/Tutorials/useHandleTutorials.tsx index 2fabfa0..1df8626 100644 --- a/src/components/Tutorials/useHandleTutorials.tsx +++ b/src/components/Tutorials/useHandleTutorials.tsx @@ -1,192 +1,194 @@ -import React, { useCallback, useEffect, useState } from "react"; -import { saveToLocalStorage } from "../Apps/AppsNavBar"; -import creationImg from './img/creation.webp' -import dashboardImg from './img/dashboard.webp' -import groupsImg from './img/groups.webp' -import importantImg from './img/important.webp' -import navigationImg from './img/navigation.webp' -import overviewImg from './img/overview.webp' -import startedImg from './img/started.webp' -import obtainingImg from './img/obtaining-qort.jpg' +import { useCallback, useEffect, useState } from 'react'; +import { saveToLocalStorage } from '../Apps/AppsNavBarDesktop'; +import creationImg from './img/creation.webp'; +import dashboardImg from './img/dashboard.webp'; +import groupsImg from './img/groups.webp'; +import importantImg from './img/important.webp'; +import navigationImg from './img/navigation.webp'; +import overviewImg from './img/overview.webp'; +import startedImg from './img/started.webp'; +import obtainingImg from './img/obtaining-qort.jpg'; const checkIfGatewayIsOnline = async () => { - try { - const url = `https://ext-node.qortal.link/admin/status`; - const response = await fetch(url, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }); - const data = await response.json(); - if (data?.height) { - return true - } - return false - - } catch (error) { - return false - - } + try { + const url = `https://ext-node.qortal.link/admin/status`; + const response = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + const data = await response.json(); + if (data?.height) { + return true; + } + return false; + } catch (error) { + return false; } +}; export const useHandleTutorials = () => { const [openTutorialModal, setOpenTutorialModal] = useState(null); -const [shownTutorials, setShowTutorials] = useState(null) + const [shownTutorials, setShowTutorials] = useState(null); -useEffect(()=> { + useEffect(() => { try { - const storedData = localStorage.getItem('shown-tutorials'); + const storedData = localStorage.getItem('shown-tutorials'); - - if (storedData) { - setShowTutorials(JSON.parse(storedData)); - } else { - setShowTutorials({}) - } + if (storedData) { + setShowTutorials(JSON.parse(storedData)); + } else { + setShowTutorials({}); + } } catch (error) { - //error + //error } -}, []) + }, []); - const saveShowTutorial = useCallback((type)=> { + const saveShowTutorial = useCallback((type) => { try { - - setShowTutorials((prev)=> { - return { - ...(prev || {}), - [type]: true - } - }) - saveToLocalStorage('shown-tutorials', type, true) + setShowTutorials((prev) => { + return { + ...(prev || {}), + [type]: true, + }; + }); + saveToLocalStorage('shown-tutorials', type, true); } catch (error) { - //error + //error } - }, []) - const showTutorial = useCallback(async (type, isForce) => { - try { - const isOnline = await checkIfGatewayIsOnline() - if(!isOnline) return + }, []); + const showTutorial = useCallback( + async (type, isForce) => { + try { + const isOnline = await checkIfGatewayIsOnline(); + if (!isOnline) return; switch (type) { - case "create-account": - { - if((shownTutorials || {})['create-account'] && !isForce) return - saveShowTutorial('create-account') - setOpenTutorialModal({ - title: "Account Creation", - resource: { - name: "a-test", - service: "VIDEO", - identifier: "account-creation-hub", - poster: creationImg - }, - }); - } - break; - case "important-information": - { - if((shownTutorials || {})['important-information'] && !isForce) return - saveShowTutorial('important-information') + case 'create-account': + { + if ((shownTutorials || {})['create-account'] && !isForce) return; + saveShowTutorial('create-account'); + setOpenTutorialModal({ + title: 'Account Creation', + resource: { + name: 'a-test', + service: 'VIDEO', + identifier: 'account-creation-hub', + poster: creationImg, + }, + }); + } + break; + case 'important-information': + { + if ((shownTutorials || {})['important-information'] && !isForce) + return; + saveShowTutorial('important-information'); - setOpenTutorialModal({ - title: "Important Information!", - resource: { - name: "a-test", - service: "VIDEO", - identifier: "important-information-hub", - poster: importantImg - }, - }); - } - break; - case "getting-started": - { - if((shownTutorials || {})['getting-started'] && !isForce) return - saveShowTutorial('getting-started') + setOpenTutorialModal({ + title: 'Important Information!', + resource: { + name: 'a-test', + service: 'VIDEO', + identifier: 'important-information-hub', + poster: importantImg, + }, + }); + } + break; + case 'getting-started': + { + if ((shownTutorials || {})['getting-started'] && !isForce) return; + saveShowTutorial('getting-started'); - setOpenTutorialModal({ - multi: [ - - { - title: "1. Getting Started", - resource: { - name: "a-test", - service: "VIDEO", - identifier: "getting-started-hub", - poster: startedImg - }, - }, - { - title: "2. Overview", - resource: { - name: "a-test", - service: "VIDEO", - identifier: "overview-hub", - poster: overviewImg - }, - }, - { - title: "3. Qortal Groups", - resource: { - name: "a-test", - service: "VIDEO", - identifier: "groups-hub", - poster: groupsImg - }, - }, - { - title: "4. Obtaining Qort", - resource: { - name: "a-test", - service: "VIDEO", - identifier: "obtaining-qort", - poster: obtainingImg - }, - }, - ], - }); - } - break; - case "qapps": + setOpenTutorialModal({ + multi: [ { - if((shownTutorials || {})['qapps'] && !isForce) return - saveShowTutorial('qapps') + title: '1. Getting Started', + resource: { + name: 'a-test', + service: 'VIDEO', + identifier: 'getting-started-hub', + poster: startedImg, + }, + }, + { + title: '2. Overview', + resource: { + name: 'a-test', + service: 'VIDEO', + identifier: 'overview-hub', + poster: overviewImg, + }, + }, + { + title: '3. Qortal Groups', + resource: { + name: 'a-test', + service: 'VIDEO', + identifier: 'groups-hub', + poster: groupsImg, + }, + }, + { + title: '4. Obtaining Qort', + resource: { + name: 'a-test', + service: 'VIDEO', + identifier: 'obtaining-qort', + poster: obtainingImg, + }, + }, + ], + }); + } + break; + case 'qapps': + { + if ((shownTutorials || {})['qapps'] && !isForce) return; + saveShowTutorial('qapps'); - setOpenTutorialModal({ - multi: [ - { - title: "1. Apps Dashboard", - resource: { - name: "a-test", - service: "VIDEO", - identifier: "apps-dashboard-hub", - poster: dashboardImg - }, - }, - { - title: "2. Apps Navigation", - resource: { - name: "a-test", - service: "VIDEO", - identifier: "apps-navigation-hub", - poster: navigationImg - }, - } - ], - }); - } - break; - default: - break; - } - } catch (error) { + setOpenTutorialModal({ + multi: [ + { + title: '1. Apps Dashboard', + resource: { + name: 'a-test', + service: 'VIDEO', + identifier: 'apps-dashboard-hub', + poster: dashboardImg, + }, + }, + { + title: '2. Apps Navigation', + resource: { + name: 'a-test', + service: 'VIDEO', + identifier: 'apps-navigation-hub', + poster: navigationImg, + }, + }, + ], + }); + } + break; + default: + break; + } + } catch (error) { //error - } - }, [shownTutorials]); + } + }, + [shownTutorials] + ); return { showTutorial, - hasSeenGettingStarted: shownTutorials === null ? null : !!(shownTutorials || {})['getting-started'], + hasSeenGettingStarted: + shownTutorials === null + ? null + : !!(shownTutorials || {})['getting-started'], openTutorialModal, setOpenTutorialModal, - shownTutorialsInitiated: !!shownTutorials + shownTutorialsInitiated: !!shownTutorials, }; }; diff --git a/src/constants/forum.ts b/src/constants/forum.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/useAppFullscreen.tsx b/src/useAppFullscreen.tsx index 3dbe4a1..1ce6842 100644 --- a/src/useAppFullscreen.tsx +++ b/src/useAppFullscreen.tsx @@ -1,67 +1,85 @@ import { useCallback, useEffect } from 'react'; -import { isMobile } from './App'; export const useAppFullScreen = (setFullScreen) => { - const enterFullScreen = useCallback(() => { - const element = document.documentElement; // Target the entire HTML document - if (element.requestFullscreen) { - element.requestFullscreen(); - } else if (element.mozRequestFullScreen) { // Firefox - element.mozRequestFullScreen(); - } else if (element.webkitRequestFullscreen) { // Chrome, Safari and Opera - element.webkitRequestFullscreen(); - } else if (element.msRequestFullscreen) { // IE/Edge - element.msRequestFullscreen(); - } - }, []); + const enterFullScreen = useCallback(() => { + const element = document.documentElement; // Target the entire HTML document + if (element.requestFullscreen) { + element.requestFullscreen(); + } else if (element.mozRequestFullScreen) { + // Firefox + element.mozRequestFullScreen(); + } else if (element.webkitRequestFullscreen) { + // Chrome, Safari and Opera + element.webkitRequestFullscreen(); + } else if (element.msRequestFullscreen) { + // IE/Edge + element.msRequestFullscreen(); + } + }, []); - const exitFullScreen = useCallback(() => { - if (document.fullscreenElement) { - document.exitFullscreen(); - } else if (document.mozFullScreenElement) { - document.mozCancelFullScreen(); - } else if (document.webkitFullscreenElement) { - document.webkitExitFullscreen(); - } else if (document.msFullscreenElement) { - document.msExitFullscreen(); - } - }, []); + const exitFullScreen = useCallback(() => { + if (document.fullscreenElement) { + document.exitFullscreen(); + } else if (document.mozFullScreenElement) { + document.mozCancelFullScreen(); + } else if (document.webkitFullscreenElement) { + document.webkitExitFullscreen(); + } else if (document.msFullscreenElement) { + document.msExitFullscreen(); + } + }, []); - const toggleFullScreen = useCallback(() => { - if(!isMobile || isMobile) return - if (document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement) { - exitFullScreen(); - setFullScreen(false) - } else { - enterFullScreen(); - setFullScreen(true) - } - }, [enterFullScreen, exitFullScreen]); + const toggleFullScreen = useCallback(() => { + if ( + document.fullscreenElement || + document.mozFullScreenElement || + document.webkitFullscreenElement || + document.msFullscreenElement + ) { + exitFullScreen(); + setFullScreen(false); + } else { + enterFullScreen(); + setFullScreen(true); + } + }, [enterFullScreen, exitFullScreen]); - // Listen for changes to fullscreen state - useEffect(() => { - const handleFullScreenChange = () => { - if (document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement) { - - } else { - setFullScreen(false); - } - }; + // Listen for changes to fullscreen state + useEffect(() => { + const handleFullScreenChange = () => { + if ( + document.fullscreenElement || + document.mozFullScreenElement || + document.webkitFullscreenElement || + document.msFullscreenElement + ) { + // TODO check empty block + } else { + setFullScreen(false); + } + }; - document.addEventListener('fullscreenchange', handleFullScreenChange); - document.addEventListener('webkitfullscreenchange', handleFullScreenChange); // Safari - document.addEventListener('mozfullscreenchange', handleFullScreenChange); // Firefox - document.addEventListener('MSFullscreenChange', handleFullScreenChange); // IE/Edge + document.addEventListener('fullscreenchange', handleFullScreenChange); + document.addEventListener('webkitfullscreenchange', handleFullScreenChange); // Safari + document.addEventListener('mozfullscreenchange', handleFullScreenChange); // Firefox + document.addEventListener('MSFullscreenChange', handleFullScreenChange); // IE/Edge - return () => { - document.removeEventListener('fullscreenchange', handleFullScreenChange); - document.removeEventListener('webkitfullscreenchange', handleFullScreenChange); - document.removeEventListener('mozfullscreenchange', handleFullScreenChange); - document.removeEventListener('MSFullscreenChange', handleFullScreenChange); - }; - }, []); + return () => { + document.removeEventListener('fullscreenchange', handleFullScreenChange); + document.removeEventListener( + 'webkitfullscreenchange', + handleFullScreenChange + ); + document.removeEventListener( + 'mozfullscreenchange', + handleFullScreenChange + ); + document.removeEventListener( + 'MSFullscreenChange', + handleFullScreenChange + ); + }; + }, []); - return { enterFullScreen, exitFullScreen, toggleFullScreen }; + return { enterFullScreen, exitFullScreen, toggleFullScreen }; }; - -