Merge pull request #18 from nbenaglia/feature/app-library-theme
Apply dark/light theme to AppLibrary page
101
android/.gitignore
vendored
@ -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
|
2
android/app/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/build/*
|
||||
!/build/.npmkeep
|
@ -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")
|
||||
}
|
@ -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()
|
||||
}
|
21
android/app/proguard-rules.pro
vendored
@ -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
|
@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@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());
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:usesCleartextTraffic="true">
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/title_activity_main"
|
||||
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths"></meta-data>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
<!-- Permissions -->
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
</manifest>
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package com.example.app;
|
||||
|
||||
import com.getcapacitor.BridgeActivity;
|
||||
|
||||
public class MainActivity extends BridgeActivity {}
|
Before Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 17 KiB |
@ -1,34 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
@ -1,170 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
</vector>
|
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 160 KiB |
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<WebView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_adaptive_back"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_adaptive_fore"/>
|
||||
</adaptive-icon>
|
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 34 KiB |
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
@ -1,7 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="app_name">Qortal </string>
|
||||
<string name="title_activity_main">Qortal </string>
|
||||
<string name="package_name">com.example.app</string>
|
||||
<string name="custom_url_scheme">com.example.app</string>
|
||||
</resources>
|
@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:background">@null</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
|
||||
<item name="android:background">@drawable/splash</item>
|
||||
</style>
|
||||
</resources>
|
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="my_images" path="." />
|
||||
<cache-path name="my_cache_images" path="." />
|
||||
</paths>
|
@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
|
||||
@Test
|
||||
public void addition_isCorrect() throws Exception {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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')
|
@ -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
|
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
@ -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
|
248
android/gradlew
vendored
@ -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" "$@"
|
92
android/gradlew.bat
vendored
@ -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
|
@ -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'
|
@ -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'
|
||||
}
|
282
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<extStates>('not-authenticated');
|
||||
const [desktopViewMode, setDesktopViewMode] = useState('home');
|
||||
|
||||
const [backupjson, setBackupjson] = useState<any>(null);
|
||||
const [rawWallet, setRawWallet] = useState<any>(null);
|
||||
const [ltcBalanceLoading, setLtcBalanceLoading] = useState<boolean>(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<HTMLInputElement>(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 (
|
||||
<AuthenticatedContainerInnerLeft
|
||||
sx={{
|
||||
overflowY: isMobile && 'auto',
|
||||
overflowY: 'auto',
|
||||
padding: '0px 20px',
|
||||
minWidth: '225px',
|
||||
}}
|
||||
@ -1568,27 +1515,9 @@ function App() {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
width: isMobile ? '100vw' : 'auto',
|
||||
width: 'auto',
|
||||
}}
|
||||
>
|
||||
{isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
padding: '10px',
|
||||
}}
|
||||
>
|
||||
<CloseIcon
|
||||
onClick={() => {
|
||||
setIsOpenDrawerProfile(false);
|
||||
}}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{desktopViewMode !== 'apps' &&
|
||||
desktopViewMode !== 'dev' &&
|
||||
desktopViewMode !== 'chat' && <>{renderProfileLeft()}</>}
|
||||
@ -1609,51 +1538,43 @@ function App() {
|
||||
>
|
||||
<Spacer height="20px" />
|
||||
|
||||
{!isMobile && (
|
||||
<>
|
||||
<Spacer height="20px" />
|
||||
<Tooltip
|
||||
title={
|
||||
<span
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
LOG OUT
|
||||
</span>
|
||||
}
|
||||
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,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Logout
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
logoutFunc();
|
||||
setIsOpenDrawerProfile(false);
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
title={
|
||||
<span
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
width: '20px',
|
||||
height: 'auto',
|
||||
fontSize: '14px',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
onClick={() => {
|
||||
logoutFunc();
|
||||
setIsOpenDrawerProfile(false);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
LOGOUT
|
||||
</span>
|
||||
}
|
||||
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,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<LogoutIcon />
|
||||
</Tooltip>
|
||||
</ButtonBase>
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
||||
@ -1964,41 +1885,39 @@ function App() {
|
||||
)}
|
||||
|
||||
<Spacer height="20px" />
|
||||
<Tooltip
|
||||
title={
|
||||
<span style={{ fontSize: '14px', fontWeight: 700 }}>
|
||||
BACKUP WALLET
|
||||
</span>
|
||||
}
|
||||
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,
|
||||
},
|
||||
},
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setExtstate('download-wallet');
|
||||
setIsOpenDrawerProfile(false);
|
||||
}}
|
||||
>
|
||||
<Download
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
width: '20px',
|
||||
height: 'auto',
|
||||
<Tooltip
|
||||
title={
|
||||
<span style={{ fontSize: '14px', fontWeight: 700 }}>
|
||||
BACKUP WALLET
|
||||
</span>
|
||||
}
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
>
|
||||
<DownloadIcon />
|
||||
</Tooltip>
|
||||
</ButtonBase>
|
||||
<Spacer height="40px" />
|
||||
</Box>
|
||||
</AuthenticatedContainerInnerRight>
|
||||
@ -2009,7 +1928,7 @@ function App() {
|
||||
return (
|
||||
<AppContainer
|
||||
sx={{
|
||||
height: isMobile ? '100%' : '100vh',
|
||||
height: '100vh',
|
||||
// backgroundImage: desktopViewMode === "apps" && 'url("appsBg.svg")',
|
||||
// backgroundSize: desktopViewMode === "apps" && "cover",
|
||||
// backgroundPosition: desktopViewMode === "apps" && "center",
|
||||
@ -2075,51 +1994,50 @@ function App() {
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100vw',
|
||||
height: isMobile ? '100%' : '100vh',
|
||||
display: 'flex',
|
||||
flexDirection: isMobile ? 'column' : 'row',
|
||||
overflow: isMobile && 'hidden',
|
||||
flexDirection: 'row',
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
}}
|
||||
>
|
||||
<Group
|
||||
logoutFunc={logoutFunc}
|
||||
balance={balance}
|
||||
userInfo={userInfo}
|
||||
myAddress={address}
|
||||
desktopViewMode={desktopViewMode}
|
||||
isFocused={isFocused}
|
||||
isMain={isMain}
|
||||
isOpenDrawerProfile={isOpenDrawerProfile}
|
||||
setIsOpenDrawerProfile={setIsOpenDrawerProfile}
|
||||
desktopViewMode={desktopViewMode}
|
||||
logoutFunc={logoutFunc}
|
||||
myAddress={address}
|
||||
setDesktopViewMode={setDesktopViewMode}
|
||||
setIsOpenDrawerProfile={setIsOpenDrawerProfile}
|
||||
userInfo={userInfo}
|
||||
/>
|
||||
{!isMobile && renderProfile()}
|
||||
{renderProfile()}
|
||||
</Box>
|
||||
</MyContext.Provider>
|
||||
)}
|
||||
{isOpenSendQort && isMainWindow && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'fixed',
|
||||
alignItems: 'center',
|
||||
background: theme.palette.background.default,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
position: 'fixed',
|
||||
width: '100%',
|
||||
zIndex: 10000,
|
||||
}}
|
||||
>
|
||||
<Spacer height="22px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
justifyContent: 'flex-start',
|
||||
paddingLeft: '22px',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
maxWidth: '700px',
|
||||
paddingLeft: '22px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Return
|
||||
@ -2130,7 +2048,9 @@ function App() {
|
||||
onClick={returnToMain}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Spacer height="35px" />
|
||||
|
||||
<QortPayment
|
||||
balance={balance}
|
||||
show={show}
|
||||
|
@ -1,32 +0,0 @@
|
||||
import { useTheme } from '@mui/material';
|
||||
import { SVGProps } from './interfaces';
|
||||
|
||||
export const Download: React.FC<SVGProps> = ({
|
||||
color,
|
||||
opacity,
|
||||
...children
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const setColor = color ? color : theme.palette.text.primary;
|
||||
const setOpacity = opacity ? opacity : 1;
|
||||
|
||||
return (
|
||||
<svg
|
||||
{...children}
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill={setColor}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12.8047 0.393196V7.21185H16.3036L10.0003 13.5139L3.69697 7.21185H7.19584V0H12.8045L12.8047 0.393196ZM2.7047 16.8587V13.9861H0V18.6179C0 19.3774 0.622589 20 1.38213 20H18.6179C19.3774 20 20 19.3774 20 18.6179V13.9861H17.2962V17.2963L2.70461 17.2954L2.7047 16.8587Z"
|
||||
fill={setColor}
|
||||
fill-opacity={setOpacity}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -1,28 +0,0 @@
|
||||
import { useTheme } from '@mui/material';
|
||||
import { SVGProps } from './interfaces';
|
||||
|
||||
export const Logout: React.FC<SVGProps> = ({ color, opacity, ...children }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const setColor = color ? color : theme.palette.text.primary;
|
||||
const setOpacity = opacity ? opacity : 1;
|
||||
|
||||
return (
|
||||
<svg
|
||||
{...children}
|
||||
width="18"
|
||||
height="20"
|
||||
viewBox="0 0 18 20"
|
||||
fill={setColor}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.56485 0H16.3611C17.2662 0 18 0.727797 18 1.62558V18.3744C18 19.2722 17.2662 20 16.3611 20H7.56485C6.65969 20 5.92593 19.2722 5.92593 18.3744V12.6013H10.6168C11.4569 12.6013 12.1404 11.9039 12.1404 11.0467V8.87329C12.1404 8.01613 11.4569 7.31875 10.6168 7.31875H5.92593V1.62558C5.92593 0.727797 6.65969 0 7.56485 0ZM11.1667 11.0467C11.1667 11.3719 10.9205 11.6354 10.6168 11.6354H4.8144C4.74521 11.6354 4.68911 11.6955 4.68911 11.7696V12.8632C4.68911 13.3492 4.17007 13.6259 3.8078 13.3329L0.218431 10.4298C-0.0728102 10.1942 -0.0728102 9.72579 0.218431 9.49024L3.8078 6.58709C4.17005 6.29409 4.68911 6.57077 4.68911 7.05684V8.1504C4.68911 8.2245 4.74521 8.28454 4.8144 8.28454H10.6168C10.9205 8.28454 11.1667 8.54813 11.1667 8.87329V11.0467Z"
|
||||
fill={setColor}
|
||||
fill-opacity={setOpacity}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -1,18 +0,0 @@
|
||||
export const LogoutIcon = ({ color, height = 20, width = 18 }) => {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 18 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.4351 0H1.63891C0.733765 0 0 0.727797 0 1.62558V18.3744C0 19.2722 0.733765 20 1.63891 20H10.4351C11.3403 20 12.0741 19.2722 12.0741 18.3744V12.6013H7.38321C6.54312 12.6013 5.85964 11.9039 5.85964 11.0467V8.87329C5.85964 8.01613 6.54312 7.31875 7.38323 7.31875H12.0741V1.62558C12.0741 0.727797 11.3403 0 10.4351 0ZM6.83334 11.0467C6.83334 11.3719 7.07952 11.6354 7.38321 11.6354H13.1856C13.2548 11.6354 13.3109 11.6955 13.3109 11.7696V12.8632C13.3109 13.3492 13.8299 13.6259 14.1922 13.3329L17.7816 10.4298C18.0728 10.1942 18.0728 9.72579 17.7816 9.49024L14.1922 6.58709C13.8299 6.29409 13.3109 6.57077 13.3109 7.05684V8.1504C13.3109 8.2245 13.2548 8.28454 13.1856 8.28454H7.38322C7.07952 8.28454 6.83334 8.54813 6.83334 8.87329V11.0467Z"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -25,7 +25,7 @@ export const NavAdd: React.FC<SVGProps> = ({ color, opacity, ...children }) => {
|
||||
<path
|
||||
d="M30 21.6666H21.6666V30C21.6666 30.9166 20.9166 31.6666 20 31.6666C19.0833 31.6666 18.3333 30.9166 18.3333 30V21.6666H9.99998C9.08331 21.6666 8.33331 20.9166 8.33331 20C8.33331 19.0833 9.08331 18.3333 9.99998 18.3333H18.3333V9.99995C18.3333 9.08328 19.0833 8.33328 20 8.33328C20.9166 8.33328 21.6666 9.08328 21.6666 9.99995V18.3333H30C30.9166 18.3333 31.6666 19.0833 31.6666 20C31.6666 20.9166 30.9166 21.6666 30 21.6666Z"
|
||||
fill={setColor}
|
||||
fill-opacity={setOpacity}
|
||||
fillOpacity={setOpacity}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
@ -32,14 +32,14 @@ export const NavCloseTab: React.FC<SVGProps> = ({
|
||||
stroke={theme.palette.text.primary}
|
||||
stroke-width="2"
|
||||
fill={setColor}
|
||||
fill-opacity={setOpacity}
|
||||
fillOpacity={setOpacity}
|
||||
/>
|
||||
<path
|
||||
d="M11.3333 5.66675L5.66658 11.3334"
|
||||
stroke={theme.palette.text.primary}
|
||||
stroke-width="2"
|
||||
fill={setColor}
|
||||
fill-opacity={setOpacity}
|
||||
fillOpacity={setOpacity}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
@ -23,7 +23,7 @@ export const NavMoreMenu: React.FC<SVGProps> = ({
|
||||
<path
|
||||
d="M8.49996 14.1666C6.94163 14.1666 5.66663 15.4416 5.66663 16.9999C5.66663 18.5583 6.94163 19.8333 8.49996 19.8333C10.0583 19.8333 11.3333 18.5583 11.3333 16.9999C11.3333 15.4416 10.0583 14.1666 8.49996 14.1666ZM25.5 14.1666C23.9416 14.1666 22.6666 15.4416 22.6666 16.9999C22.6666 18.5583 23.9416 19.8333 25.5 19.8333C27.0583 19.8333 28.3333 18.5583 28.3333 16.9999C28.3333 15.4416 27.0583 14.1666 25.5 14.1666ZM17 14.1666C15.4416 14.1666 14.1666 15.4416 14.1666 16.9999C14.1666 18.5583 15.4416 19.8333 17 19.8333C18.5583 19.8333 19.8333 18.5583 19.8333 16.9999C19.8333 15.4416 18.5583 14.1666 17 14.1666Z"
|
||||
fill={setColor}
|
||||
fill-opacity={setOpacity}
|
||||
fillOpacity={setOpacity}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
30
src/assets/Icons/QappDevelopText.tsx
Normal file
34
src/assets/Icons/QappLibraryText.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { useTheme } from '@mui/material';
|
||||
import { SVGProps } from './interfaces';
|
||||
|
||||
export const QappLibraryText: React.FC<SVGProps> = ({
|
||||
color,
|
||||
opacity,
|
||||
...children
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const setColor = color ? color : theme.palette.text.primary;
|
||||
const setOpacity = opacity ? opacity : 1;
|
||||
|
||||
return (
|
||||
<svg
|
||||
{...children}
|
||||
width="301"
|
||||
height="26"
|
||||
viewBox="0 0 301 26"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.5 15.3636H14.4091L16.1818 17.5909L19.1818 21L23.0909 25.7273H17.5L14.7273 22.5L12.8636 19.8182L9.5 15.3636ZM23.0455 12.3636C23.0455 14.9545 22.5417 17.1402 21.5341 18.9205C20.5265 20.6932 19.1667 22.0379 17.4545 22.9545C15.7424 23.8636 13.8333 24.3182 11.7273 24.3182C9.60606 24.3182 7.68939 23.8598 5.97727 22.9432C4.27273 22.0189 2.91667 20.6705 1.90909 18.8977C0.909091 17.1174 0.409091 14.9394 0.409091 12.3636C0.409091 9.77273 0.909091 7.59091 1.90909 5.81818C2.91667 4.03788 4.27273 2.69318 5.97727 1.78409C7.68939 0.867423 9.60606 0.40909 11.7273 0.40909C13.8333 0.40909 15.7424 0.867423 17.4545 1.78409C19.1667 2.69318 20.5265 4.03788 21.5341 5.81818C22.5417 7.59091 23.0455 9.77273 23.0455 12.3636ZM16.5455 12.3636C16.5455 10.9697 16.3598 9.79545 15.9886 8.84091C15.625 7.87879 15.0833 7.15151 14.3636 6.65909C13.6515 6.15909 12.7727 5.90909 11.7273 5.90909C10.6818 5.90909 9.79924 6.15909 9.07955 6.65909C8.36742 7.15151 7.82576 7.87879 7.45455 8.84091C7.09091 9.79545 6.90909 10.9697 6.90909 12.3636C6.90909 13.7576 7.09091 14.9356 7.45455 15.8977C7.82576 16.8523 8.36742 17.5795 9.07955 18.0795C9.79924 18.572 10.6818 18.8182 11.7273 18.8182C12.7727 18.8182 13.6515 18.572 14.3636 18.0795C15.0833 17.5795 15.625 16.8523 15.9886 15.8977C16.3598 14.9356 16.5455 13.7576 16.5455 12.3636ZM39.5455 10V14.7273H28.6364V10H39.5455ZM51.233 24H44.4148L52.0966 0.727272H60.733L68.4148 24H61.5966L56.5057 7.13636H56.3239L51.233 24ZM49.9602 14.8182H62.7784V19.5455H49.9602V14.8182ZM72.6562 24V0.727272H82.7017C84.429 0.727272 85.9403 1.06818 87.2358 1.75C88.5313 2.43182 89.5388 3.39015 90.2585 4.625C90.9782 5.85985 91.3381 7.30303 91.3381 8.95455C91.3381 10.6212 90.9669 12.0644 90.2244 13.2841C89.4896 14.5038 88.4555 15.4432 87.1222 16.1023C85.7964 16.7614 84.2472 17.0909 82.4744 17.0909H76.4744V12.1818H81.2017C81.9441 12.1818 82.5767 12.053 83.0994 11.7955C83.6297 11.5303 84.035 11.1553 84.3153 10.6705C84.6032 10.1856 84.7472 9.61364 84.7472 8.95455C84.7472 8.28788 84.6032 7.7197 84.3153 7.25C84.035 6.77273 83.6297 6.40909 83.0994 6.15909C82.5767 5.90151 81.9441 5.77273 81.2017 5.77273H78.9744V24H72.6562ZM95.6562 24V0.727272H105.702C107.429 0.727272 108.94 1.06818 110.236 1.75C111.531 2.43182 112.539 3.39015 113.259 4.625C113.978 5.85985 114.338 7.30303 114.338 8.95455C114.338 10.6212 113.967 12.0644 113.224 13.2841C112.49 14.5038 111.455 15.4432 110.122 16.1023C108.796 16.7614 107.247 17.0909 105.474 17.0909H99.4744V12.1818H104.202C104.944 12.1818 105.577 12.053 106.099 11.7955C106.63 11.5303 107.035 11.1553 107.315 10.6705C107.603 10.1856 107.747 9.61364 107.747 8.95455C107.747 8.28788 107.603 7.7197 107.315 7.25C107.035 6.77273 106.63 6.40909 106.099 6.15909C105.577 5.90151 104.944 5.77273 104.202 5.77273H101.974V24H95.6562ZM131.202 8C131.141 7.24242 130.857 6.65151 130.349 6.22727C129.849 5.80303 129.088 5.59091 128.065 5.59091C127.414 5.59091 126.88 5.67045 126.463 5.82954C126.054 5.98106 125.751 6.18939 125.554 6.45454C125.357 6.7197 125.255 7.02273 125.247 7.36364C125.232 7.64394 125.281 7.89773 125.395 8.125C125.516 8.3447 125.705 8.54545 125.963 8.72727C126.221 8.90151 126.55 9.06061 126.952 9.20455C127.353 9.34848 127.83 9.47727 128.384 9.59091L130.293 10C131.58 10.2727 132.683 10.6326 133.599 11.0795C134.516 11.5265 135.266 12.053 135.849 12.6591C136.433 13.2576 136.861 13.9318 137.134 14.6818C137.414 15.4318 137.558 16.25 137.565 17.1364C137.558 18.6667 137.175 19.9621 136.418 21.0227C135.66 22.0833 134.577 22.8902 133.168 23.4432C131.766 23.9962 130.08 24.2727 128.111 24.2727C126.088 24.2727 124.323 23.9735 122.815 23.375C121.315 22.7765 120.149 21.8561 119.315 20.6136C118.49 19.3636 118.073 17.7652 118.065 15.8182H124.065C124.103 16.5303 124.281 17.1288 124.599 17.6136C124.918 18.0985 125.365 18.4659 125.94 18.7159C126.524 18.9659 127.217 19.0909 128.02 19.0909C128.694 19.0909 129.259 19.0076 129.713 18.8409C130.168 18.6742 130.512 18.4432 130.747 18.1477C130.982 17.8523 131.103 17.5152 131.111 17.1364C131.103 16.7803 130.986 16.4697 130.759 16.2045C130.539 15.9318 130.175 15.6894 129.668 15.4773C129.16 15.2576 128.474 15.053 127.611 14.8636L125.293 14.3636C123.232 13.9167 121.607 13.1705 120.418 12.125C119.236 11.072 118.649 9.63636 118.656 7.81818C118.649 6.34091 119.043 5.04924 119.838 3.94318C120.641 2.82954 121.751 1.96212 123.168 1.34091C124.592 0.719696 126.224 0.40909 128.065 0.40909C129.944 0.40909 131.569 0.723484 132.94 1.35227C134.312 1.98106 135.368 2.86742 136.111 4.01136C136.861 5.14773 137.24 6.47727 137.247 8H131.202Z"
|
||||
fill={setColor}
|
||||
opacity={setOpacity}
|
||||
/>
|
||||
<path
|
||||
d="M150.344 24V0.727272H156.662V18.9091H166.071V24H150.344ZM176.943 0.727272V24H170.625V0.727272H176.943ZM181.938 24V0.727272H192.028C193.801 0.727272 195.29 0.965909 196.494 1.44318C197.706 1.92045 198.619 2.5947 199.233 3.46591C199.854 4.33712 200.165 5.36364 200.165 6.54545C200.165 7.40151 199.975 8.18182 199.597 8.88636C199.225 9.59091 198.703 10.1818 198.028 10.6591C197.354 11.1288 196.566 11.4545 195.665 11.6364V11.8636C196.665 11.9015 197.574 12.1553 198.392 12.625C199.21 13.0871 199.862 13.7273 200.347 14.5455C200.831 15.3561 201.074 16.3106 201.074 17.4091C201.074 18.6818 200.741 19.8144 200.074 20.8068C199.415 21.7992 198.475 22.5795 197.256 23.1477C196.036 23.7159 194.581 24 192.892 24H181.938ZM188.256 18.9545H191.21C192.271 18.9545 193.066 18.7576 193.597 18.3636C194.127 17.9621 194.392 17.3712 194.392 16.5909C194.392 16.0455 194.267 15.5833 194.017 15.2045C193.767 14.8258 193.411 14.5379 192.949 14.3409C192.494 14.1439 191.945 14.0455 191.301 14.0455H188.256V18.9545ZM188.256 10.1364H190.847C191.4 10.1364 191.888 10.0492 192.312 9.875C192.737 9.70076 193.066 9.45076 193.301 9.125C193.544 8.79167 193.665 8.38636 193.665 7.90909C193.665 7.18939 193.407 6.64015 192.892 6.26136C192.377 5.875 191.725 5.68182 190.938 5.68182H188.256V10.1364ZM205.312 24V0.727272H215.358C217.085 0.727272 218.597 1.04167 219.892 1.67045C221.188 2.29924 222.195 3.20455 222.915 4.38636C223.634 5.56818 223.994 6.98485 223.994 8.63636C223.994 10.303 223.623 11.7083 222.881 12.8523C222.146 13.9962 221.112 14.8598 219.778 15.4432C218.453 16.0265 216.903 16.3182 215.131 16.3182H209.131V11.4091H213.858C214.6 11.4091 215.233 11.3182 215.756 11.1364C216.286 10.947 216.691 10.6477 216.972 10.2386C217.259 9.82955 217.403 9.29545 217.403 8.63636C217.403 7.9697 217.259 7.42803 216.972 7.01136C216.691 6.58712 216.286 6.27651 215.756 6.07954C215.233 5.875 214.6 5.77273 213.858 5.77273H211.631V24H205.312ZM218.949 13.3182L224.767 24H217.903L212.222 13.3182H218.949ZM234.733 24H227.915L235.597 0.727272H244.233L251.915 24H245.097L240.006 7.13636H239.824L234.733 24ZM233.46 14.8182H246.278V19.5455H233.46V14.8182ZM256.156 24V0.727272H266.202C267.929 0.727272 269.44 1.04167 270.736 1.67045C272.031 2.29924 273.039 3.20455 273.759 4.38636C274.478 5.56818 274.838 6.98485 274.838 8.63636C274.838 10.303 274.467 11.7083 273.724 12.8523C272.99 13.9962 271.955 14.8598 270.622 15.4432C269.296 16.0265 267.747 16.3182 265.974 16.3182H259.974V11.4091H264.702C265.444 11.4091 266.077 11.3182 266.599 11.1364C267.13 10.947 267.535 10.6477 267.815 10.2386C268.103 9.82955 268.247 9.29545 268.247 8.63636C268.247 7.9697 268.103 7.42803 267.815 7.01136C267.535 6.58712 267.13 6.27651 266.599 6.07954C266.077 5.875 265.444 5.77273 264.702 5.77273H262.474V24H256.156ZM269.793 13.3182L275.611 24H268.747L263.065 13.3182H269.793ZM277.483 0.727272H284.528L289.074 10.1818H289.256L293.801 0.727272H300.847L292.301 16.6818V24H286.028V16.6818L277.483 0.727272Z"
|
||||
fill="#0091E1"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -20,7 +20,7 @@ export const Return: React.FC<SVGProps> = ({ 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}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
26
src/assets/Icons/Search.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { useTheme } from '@mui/material';
|
||||
import { SVGProps } from './interfaces';
|
||||
|
||||
export const Search: React.FC<SVGProps> = ({ color, opacity, ...children }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const setColor = color ? color : theme.palette.text.primary;
|
||||
const setOpacity = opacity ? opacity : 1;
|
||||
|
||||
return (
|
||||
<svg
|
||||
{...children}
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.08728 0.00158245C2.72507 0.00158245 0 2.7262 0 6.08784C0 9.44948 2.72507 12.1741 6.08728 12.1741C7.62099 12.1741 9.02317 11.6043 10.0947 10.6668L13.3088 13.8803C13.3881 13.9596 13.4911 14 13.595 14C13.6988 14 13.8018 13.9596 13.8811 13.8803C14.0396 13.7218 14.0396 13.4643 13.8811 13.3066L10.667 10.093C11.6047 9.02162 12.1746 7.62202 12.1746 6.08626C12.1746 2.72461 9.44951 0 6.0873 0L6.08728 0.00158245ZM6.08728 11.3626C3.17756 11.3626 0.811637 8.99707 0.811637 6.08784C0.811637 3.17861 3.17756 0.813083 6.08728 0.813083C8.997 0.813083 11.3629 3.17861 11.3629 6.08784C11.3629 8.99707 8.997 11.3626 6.08728 11.3626Z"
|
||||
fill={setColor}
|
||||
opacity={setOpacity}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 13 KiB |
@ -1,4 +0,0 @@
|
||||
<svg width="301" height="26" viewBox="0 0 301 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.5 15.3636H14.4091L16.1818 17.5909L19.1818 21L23.0909 25.7273H17.5L14.7273 22.5L12.8636 19.8182L9.5 15.3636ZM23.0455 12.3636C23.0455 14.9545 22.5417 17.1402 21.5341 18.9205C20.5265 20.6932 19.1667 22.0379 17.4545 22.9545C15.7424 23.8636 13.8333 24.3182 11.7273 24.3182C9.60606 24.3182 7.68939 23.8598 5.97727 22.9432C4.27273 22.0189 2.91667 20.6705 1.90909 18.8977C0.909091 17.1174 0.409091 14.9394 0.409091 12.3636C0.409091 9.77273 0.909091 7.59091 1.90909 5.81818C2.91667 4.03788 4.27273 2.69318 5.97727 1.78409C7.68939 0.867423 9.60606 0.40909 11.7273 0.40909C13.8333 0.40909 15.7424 0.867423 17.4545 1.78409C19.1667 2.69318 20.5265 4.03788 21.5341 5.81818C22.5417 7.59091 23.0455 9.77273 23.0455 12.3636ZM16.5455 12.3636C16.5455 10.9697 16.3598 9.79545 15.9886 8.84091C15.625 7.87879 15.0833 7.15151 14.3636 6.65909C13.6515 6.15909 12.7727 5.90909 11.7273 5.90909C10.6818 5.90909 9.79924 6.15909 9.07955 6.65909C8.36742 7.15151 7.82576 7.87879 7.45455 8.84091C7.09091 9.79545 6.90909 10.9697 6.90909 12.3636C6.90909 13.7576 7.09091 14.9356 7.45455 15.8977C7.82576 16.8523 8.36742 17.5795 9.07955 18.0795C9.79924 18.572 10.6818 18.8182 11.7273 18.8182C12.7727 18.8182 13.6515 18.572 14.3636 18.0795C15.0833 17.5795 15.625 16.8523 15.9886 15.8977C16.3598 14.9356 16.5455 13.7576 16.5455 12.3636ZM39.5455 10V14.7273H28.6364V10H39.5455ZM51.233 24H44.4148L52.0966 0.727272H60.733L68.4148 24H61.5966L56.5057 7.13636H56.3239L51.233 24ZM49.9602 14.8182H62.7784V19.5455H49.9602V14.8182ZM72.6562 24V0.727272H82.7017C84.429 0.727272 85.9403 1.06818 87.2358 1.75C88.5313 2.43182 89.5388 3.39015 90.2585 4.625C90.9782 5.85985 91.3381 7.30303 91.3381 8.95455C91.3381 10.6212 90.9669 12.0644 90.2244 13.2841C89.4896 14.5038 88.4555 15.4432 87.1222 16.1023C85.7964 16.7614 84.2472 17.0909 82.4744 17.0909H76.4744V12.1818H81.2017C81.9441 12.1818 82.5767 12.053 83.0994 11.7955C83.6297 11.5303 84.035 11.1553 84.3153 10.6705C84.6032 10.1856 84.7472 9.61364 84.7472 8.95455C84.7472 8.28788 84.6032 7.7197 84.3153 7.25C84.035 6.77273 83.6297 6.40909 83.0994 6.15909C82.5767 5.90151 81.9441 5.77273 81.2017 5.77273H78.9744V24H72.6562ZM95.6562 24V0.727272H105.702C107.429 0.727272 108.94 1.06818 110.236 1.75C111.531 2.43182 112.539 3.39015 113.259 4.625C113.978 5.85985 114.338 7.30303 114.338 8.95455C114.338 10.6212 113.967 12.0644 113.224 13.2841C112.49 14.5038 111.455 15.4432 110.122 16.1023C108.796 16.7614 107.247 17.0909 105.474 17.0909H99.4744V12.1818H104.202C104.944 12.1818 105.577 12.053 106.099 11.7955C106.63 11.5303 107.035 11.1553 107.315 10.6705C107.603 10.1856 107.747 9.61364 107.747 8.95455C107.747 8.28788 107.603 7.7197 107.315 7.25C107.035 6.77273 106.63 6.40909 106.099 6.15909C105.577 5.90151 104.944 5.77273 104.202 5.77273H101.974V24H95.6562ZM131.202 8C131.141 7.24242 130.857 6.65151 130.349 6.22727C129.849 5.80303 129.088 5.59091 128.065 5.59091C127.414 5.59091 126.88 5.67045 126.463 5.82954C126.054 5.98106 125.751 6.18939 125.554 6.45454C125.357 6.7197 125.255 7.02273 125.247 7.36364C125.232 7.64394 125.281 7.89773 125.395 8.125C125.516 8.3447 125.705 8.54545 125.963 8.72727C126.221 8.90151 126.55 9.06061 126.952 9.20455C127.353 9.34848 127.83 9.47727 128.384 9.59091L130.293 10C131.58 10.2727 132.683 10.6326 133.599 11.0795C134.516 11.5265 135.266 12.053 135.849 12.6591C136.433 13.2576 136.861 13.9318 137.134 14.6818C137.414 15.4318 137.558 16.25 137.565 17.1364C137.558 18.6667 137.175 19.9621 136.418 21.0227C135.66 22.0833 134.577 22.8902 133.168 23.4432C131.766 23.9962 130.08 24.2727 128.111 24.2727C126.088 24.2727 124.323 23.9735 122.815 23.375C121.315 22.7765 120.149 21.8561 119.315 20.6136C118.49 19.3636 118.073 17.7652 118.065 15.8182H124.065C124.103 16.5303 124.281 17.1288 124.599 17.6136C124.918 18.0985 125.365 18.4659 125.94 18.7159C126.524 18.9659 127.217 19.0909 128.02 19.0909C128.694 19.0909 129.259 19.0076 129.713 18.8409C130.168 18.6742 130.512 18.4432 130.747 18.1477C130.982 17.8523 131.103 17.5152 131.111 17.1364C131.103 16.7803 130.986 16.4697 130.759 16.2045C130.539 15.9318 130.175 15.6894 129.668 15.4773C129.16 15.2576 128.474 15.053 127.611 14.8636L125.293 14.3636C123.232 13.9167 121.607 13.1705 120.418 12.125C119.236 11.072 118.649 9.63636 118.656 7.81818C118.649 6.34091 119.043 5.04924 119.838 3.94318C120.641 2.82954 121.751 1.96212 123.168 1.34091C124.592 0.719696 126.224 0.40909 128.065 0.40909C129.944 0.40909 131.569 0.723484 132.94 1.35227C134.312 1.98106 135.368 2.86742 136.111 4.01136C136.861 5.14773 137.24 6.47727 137.247 8H131.202Z" fill="white"/>
|
||||
<path d="M150.344 24V0.727272H156.662V18.9091H166.071V24H150.344ZM176.943 0.727272V24H170.625V0.727272H176.943ZM181.938 24V0.727272H192.028C193.801 0.727272 195.29 0.965909 196.494 1.44318C197.706 1.92045 198.619 2.5947 199.233 3.46591C199.854 4.33712 200.165 5.36364 200.165 6.54545C200.165 7.40151 199.975 8.18182 199.597 8.88636C199.225 9.59091 198.703 10.1818 198.028 10.6591C197.354 11.1288 196.566 11.4545 195.665 11.6364V11.8636C196.665 11.9015 197.574 12.1553 198.392 12.625C199.21 13.0871 199.862 13.7273 200.347 14.5455C200.831 15.3561 201.074 16.3106 201.074 17.4091C201.074 18.6818 200.741 19.8144 200.074 20.8068C199.415 21.7992 198.475 22.5795 197.256 23.1477C196.036 23.7159 194.581 24 192.892 24H181.938ZM188.256 18.9545H191.21C192.271 18.9545 193.066 18.7576 193.597 18.3636C194.127 17.9621 194.392 17.3712 194.392 16.5909C194.392 16.0455 194.267 15.5833 194.017 15.2045C193.767 14.8258 193.411 14.5379 192.949 14.3409C192.494 14.1439 191.945 14.0455 191.301 14.0455H188.256V18.9545ZM188.256 10.1364H190.847C191.4 10.1364 191.888 10.0492 192.312 9.875C192.737 9.70076 193.066 9.45076 193.301 9.125C193.544 8.79167 193.665 8.38636 193.665 7.90909C193.665 7.18939 193.407 6.64015 192.892 6.26136C192.377 5.875 191.725 5.68182 190.938 5.68182H188.256V10.1364ZM205.312 24V0.727272H215.358C217.085 0.727272 218.597 1.04167 219.892 1.67045C221.188 2.29924 222.195 3.20455 222.915 4.38636C223.634 5.56818 223.994 6.98485 223.994 8.63636C223.994 10.303 223.623 11.7083 222.881 12.8523C222.146 13.9962 221.112 14.8598 219.778 15.4432C218.453 16.0265 216.903 16.3182 215.131 16.3182H209.131V11.4091H213.858C214.6 11.4091 215.233 11.3182 215.756 11.1364C216.286 10.947 216.691 10.6477 216.972 10.2386C217.259 9.82955 217.403 9.29545 217.403 8.63636C217.403 7.9697 217.259 7.42803 216.972 7.01136C216.691 6.58712 216.286 6.27651 215.756 6.07954C215.233 5.875 214.6 5.77273 213.858 5.77273H211.631V24H205.312ZM218.949 13.3182L224.767 24H217.903L212.222 13.3182H218.949ZM234.733 24H227.915L235.597 0.727272H244.233L251.915 24H245.097L240.006 7.13636H239.824L234.733 24ZM233.46 14.8182H246.278V19.5455H233.46V14.8182ZM256.156 24V0.727272H266.202C267.929 0.727272 269.44 1.04167 270.736 1.67045C272.031 2.29924 273.039 3.20455 273.759 4.38636C274.478 5.56818 274.838 6.98485 274.838 8.63636C274.838 10.303 274.467 11.7083 273.724 12.8523C272.99 13.9962 271.955 14.8598 270.622 15.4432C269.296 16.0265 267.747 16.3182 265.974 16.3182H259.974V11.4091H264.702C265.444 11.4091 266.077 11.3182 266.599 11.1364C267.13 10.947 267.535 10.6477 267.815 10.2386C268.103 9.82955 268.247 9.29545 268.247 8.63636C268.247 7.9697 268.103 7.42803 267.815 7.01136C267.535 6.58712 267.13 6.27651 266.599 6.07954C266.077 5.875 265.444 5.77273 264.702 5.77273H262.474V24H256.156ZM269.793 13.3182L275.611 24H268.747L263.065 13.3182H269.793ZM277.483 0.727272H284.528L289.074 10.1818H289.256L293.801 0.727272H300.847L292.301 16.6818V24H286.028V16.6818L277.483 0.727272Z" fill="#0091E1"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 7.4 KiB |
@ -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<any | null> {
|
||||
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<number> => {
|
||||
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;
|
||||
|
@ -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<any>("walletInfo").catch(() => null);
|
||||
const res = await getData<any>('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<any>("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<any>('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)
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
@ -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 (
|
||||
|
@ -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 (
|
||||
<AppsLibraryContainer
|
||||
sx={{
|
||||
height: !isMobile && "100%",
|
||||
justifyContent: !isMobile && "flex-start",
|
||||
alignItems: isMobile && 'center'
|
||||
height: '100%',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
maxWidth: "500px",
|
||||
width: '90%'
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
maxWidth: '500px',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Spacer height="30px" />
|
||||
|
||||
|
||||
{!isMobile && <Spacer height="30px" />}
|
||||
<AppsWidthLimiter>
|
||||
<AppInfoSnippetContainer>
|
||||
<AppInfoSnippetLeft
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
gap: "18px",
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer
|
||||
<AppsWidthLimiter>
|
||||
<AppInfoSnippetContainer>
|
||||
<AppInfoSnippetLeft
|
||||
sx={{
|
||||
width: "auto",
|
||||
flexGrow: 1,
|
||||
gap: '18px',
|
||||
}}
|
||||
>
|
||||
<AppCircle
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
border: "none",
|
||||
height: "100px",
|
||||
width: "100px",
|
||||
width: 'auto',
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
<AppCircle
|
||||
sx={{
|
||||
height: "43px",
|
||||
width: "43px",
|
||||
"& img": {
|
||||
objectFit: "fill",
|
||||
},
|
||||
border: 'none',
|
||||
height: '100px',
|
||||
width: '100px',
|
||||
}}
|
||||
alt={app?.name}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
app?.name
|
||||
}/qortal_avatar?async=true`}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: "43px",
|
||||
height: "auto",
|
||||
<Avatar
|
||||
sx={{
|
||||
height: '43px',
|
||||
width: '43px',
|
||||
'& img': {
|
||||
objectFit: 'fill',
|
||||
},
|
||||
}}
|
||||
src={LogoSelected}
|
||||
alt="center-icon"
|
||||
/>
|
||||
</Avatar>
|
||||
</AppCircle>
|
||||
</AppCircleContainer>
|
||||
<AppInfoSnippetMiddle>
|
||||
<AppInfoAppName>
|
||||
{app?.metadata?.title || app?.name}
|
||||
</AppInfoAppName>
|
||||
<Spacer height="6px" />
|
||||
<AppInfoUserName>{app?.name}</AppInfoUserName>
|
||||
<Spacer height="3px" />
|
||||
</AppInfoSnippetMiddle>
|
||||
</AppInfoSnippetLeft>
|
||||
<AppInfoSnippetRight></AppInfoSnippetRight>
|
||||
</AppInfoSnippetContainer>
|
||||
<Spacer height="11px" />
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '20px'
|
||||
}}>
|
||||
<AppDownloadButton
|
||||
onClick={() => {
|
||||
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`}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '43px',
|
||||
height: 'auto',
|
||||
}}
|
||||
src={LogoSelected}
|
||||
alt="center-icon"
|
||||
/>
|
||||
</Avatar>
|
||||
</AppCircle>
|
||||
</AppCircleContainer>
|
||||
|
||||
<AppInfoSnippetMiddle>
|
||||
<AppInfoAppName>
|
||||
{app?.metadata?.title || app?.name}
|
||||
</AppInfoAppName>
|
||||
|
||||
<Spacer height="6px" />
|
||||
|
||||
<AppInfoUserName>{app?.name}</AppInfoUserName>
|
||||
|
||||
<Spacer height="3px" />
|
||||
</AppInfoSnippetMiddle>
|
||||
</AppInfoSnippetLeft>
|
||||
</AppInfoSnippetContainer>
|
||||
|
||||
<Spacer height="11px" />
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '20px',
|
||||
}}
|
||||
>
|
||||
<AppDownloadButton
|
||||
onClick={() => {
|
||||
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
|
||||
}}
|
||||
>
|
||||
<AppDownloadButtonText>
|
||||
{!isMobile ? (
|
||||
<>
|
||||
{isSelectedAppPinned ? 'Unpin from dashboard' : 'Pin to dashboard'}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{isSelectedAppPinned ? 'Unpin' : 'Pin'}
|
||||
</>
|
||||
)}
|
||||
|
||||
</AppDownloadButtonText>
|
||||
</AppDownloadButton>
|
||||
<AppDownloadButton
|
||||
onClick={() => {
|
||||
executeEvent("addTab", {
|
||||
data: app,
|
||||
});
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor: isInstalled ? "#0091E1" : "#247C0E",
|
||||
width: "100%",
|
||||
maxWidth: "320px",
|
||||
height: "29px",
|
||||
}}
|
||||
>
|
||||
<AppDownloadButtonText>
|
||||
{isInstalled ? "Open" : "Download"}
|
||||
</AppDownloadButtonText>
|
||||
</AppDownloadButton>
|
||||
</Box>
|
||||
|
||||
</AppsWidthLimiter>
|
||||
<Spacer height="20px" />
|
||||
<AppsWidthLimiter>
|
||||
<AppsCategoryInfo>
|
||||
<AppRating ratingCountPosition="top" myName={myName} app={app} />
|
||||
<Spacer width="16px" />
|
||||
<Spacer height="40px" width="1px" backgroundColor="white" />
|
||||
<Spacer width="16px" />
|
||||
<AppsCategoryInfoSub>
|
||||
<AppsCategoryInfoLabel>Category:</AppsCategoryInfoLabel>
|
||||
<Spacer height="4px" />
|
||||
<AppsCategoryInfoValue>
|
||||
{app?.metadata?.categoryName || "none"}
|
||||
</AppsCategoryInfoValue>
|
||||
</AppsCategoryInfoSub>
|
||||
</AppsCategoryInfo>
|
||||
<Spacer height="30px" />
|
||||
<AppInfoAppName>About this Q-App</AppInfoAppName>
|
||||
</AppsWidthLimiter>
|
||||
<Spacer height="20px" />
|
||||
<AppsInfoDescription>
|
||||
{app?.metadata?.description || "No description"}
|
||||
</AppsInfoDescription>
|
||||
return updatedApps;
|
||||
});
|
||||
setSettingsLocalLastUpdated(Date.now());
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
height: '29px',
|
||||
maxWidth: '320px',
|
||||
opacity: isSelectedAppPinned ? 0.6 : 1,
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<AppDownloadButtonText>
|
||||
{isSelectedAppPinned
|
||||
? 'Unpin from dashboard'
|
||||
: 'Pin to dashboard'}
|
||||
</AppDownloadButtonText>
|
||||
</AppDownloadButton>
|
||||
|
||||
<AppDownloadButton
|
||||
onClick={() => {
|
||||
executeEvent('addTab', {
|
||||
data: app,
|
||||
});
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor: isInstalled
|
||||
? '#0091E1'
|
||||
: theme.palette.background.paper,
|
||||
height: '29px',
|
||||
maxWidth: '320px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<AppDownloadButtonText>
|
||||
{isInstalled ? 'Open' : 'Download'}
|
||||
</AppDownloadButtonText>
|
||||
</AppDownloadButton>
|
||||
</Box>
|
||||
</AppsWidthLimiter>
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
||||
<AppsWidthLimiter>
|
||||
<AppsCategoryInfo>
|
||||
<AppRating ratingCountPosition="top" myName={myName} app={app} />
|
||||
|
||||
<Spacer width="16px" />
|
||||
|
||||
<Spacer
|
||||
backgroundColor={theme.palette.background.paper}
|
||||
height="40px"
|
||||
width="1px"
|
||||
/>
|
||||
|
||||
<Spacer width="16px" />
|
||||
|
||||
<AppsCategoryInfoSub>
|
||||
<AppsCategoryInfoLabel>Category:</AppsCategoryInfoLabel>
|
||||
|
||||
<Spacer height="4px" />
|
||||
|
||||
<AppsCategoryInfoValue>
|
||||
{app?.metadata?.categoryName || 'none'}
|
||||
</AppsCategoryInfoValue>
|
||||
</AppsCategoryInfoSub>
|
||||
</AppsCategoryInfo>
|
||||
|
||||
<Spacer height="30px" />
|
||||
|
||||
<AppInfoAppName>About this Q-App</AppInfoAppName>
|
||||
</AppsWidthLimiter>
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
||||
<AppsInfoDescription>
|
||||
{app?.metadata?.description || 'No description'}
|
||||
</AppsInfoDescription>
|
||||
</Box>
|
||||
</AppsLibraryContainer>
|
||||
);
|
||||
|
@ -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 (
|
||||
<AppInfoSnippetContainer sx={{
|
||||
...parentStyles
|
||||
}}>
|
||||
<AppInfoSnippetContainer
|
||||
sx={{
|
||||
...parentStyles,
|
||||
}}
|
||||
>
|
||||
<AppInfoSnippetLeft>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
height: "80px",
|
||||
width: "60px",
|
||||
}}
|
||||
onClick={()=> {
|
||||
if(isFromCategory){
|
||||
executeEvent("selectedAppInfoCategory", {
|
||||
<ButtonBase
|
||||
sx={{
|
||||
height: '80px',
|
||||
width: '60px',
|
||||
}}
|
||||
onClick={() => {
|
||||
if (isFromCategory) {
|
||||
executeEvent('selectedAppInfoCategory', {
|
||||
data: app,
|
||||
});
|
||||
return;
|
||||
}
|
||||
executeEvent('selectedAppInfo', {
|
||||
data: app,
|
||||
});
|
||||
return
|
||||
}
|
||||
executeEvent("selectedAppInfo", {
|
||||
data: app,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer>
|
||||
<AppCircle
|
||||
sx={{
|
||||
border: "none",
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer>
|
||||
<AppCircle
|
||||
sx={{
|
||||
border: 'none',
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
height: '42px',
|
||||
width: '42px',
|
||||
'& img': {
|
||||
objectFit: 'fill',
|
||||
},
|
||||
}}
|
||||
alt={app?.name}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
app?.name
|
||||
}/qortal_avatar?async=true`}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '31px',
|
||||
height: 'auto',
|
||||
}}
|
||||
src={LogoSelected}
|
||||
alt="center-icon"
|
||||
/>
|
||||
</Avatar>
|
||||
</AppCircle>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
|
||||
<AppInfoSnippetMiddle>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
if (isFromCategory) {
|
||||
executeEvent('selectedAppInfoCategory', {
|
||||
data: app,
|
||||
});
|
||||
return;
|
||||
}
|
||||
executeEvent('selectedAppInfo', {
|
||||
data: app,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "42px",
|
||||
width: "42px",
|
||||
'& img': {
|
||||
objectFit: 'fill',
|
||||
}
|
||||
}}
|
||||
alt={app?.name}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
app?.name
|
||||
}/qortal_avatar?async=true`}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: "31px",
|
||||
height: "auto",
|
||||
}}
|
||||
src={LogoSelected}
|
||||
alt="center-icon"
|
||||
/>
|
||||
</Avatar>
|
||||
</AppCircle>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
<AppInfoSnippetMiddle>
|
||||
|
||||
<ButtonBase onClick={()=> {
|
||||
if(isFromCategory){
|
||||
executeEvent("selectedAppInfoCategory", {
|
||||
<AppInfoAppName>{app?.metadata?.title || app?.name}</AppInfoAppName>
|
||||
</ButtonBase>
|
||||
|
||||
<Spacer height="6px" />
|
||||
|
||||
<AppInfoUserName>{app?.name}</AppInfoUserName>
|
||||
|
||||
<Spacer height="3px" />
|
||||
|
||||
<AppRating app={app} myName={myName} />
|
||||
</AppInfoSnippetMiddle>
|
||||
</AppInfoSnippetLeft>
|
||||
|
||||
<AppInfoSnippetRight
|
||||
sx={{
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<AppDownloadButton
|
||||
onClick={() => {
|
||||
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,
|
||||
}}
|
||||
>
|
||||
<AppDownloadButtonText>
|
||||
{' '}
|
||||
{isSelectedAppPinned ? 'Unpin' : 'Pin'}
|
||||
</AppDownloadButtonText>
|
||||
</AppDownloadButton>
|
||||
|
||||
<AppDownloadButton
|
||||
onClick={() => {
|
||||
executeEvent('addTab', {
|
||||
data: app,
|
||||
});
|
||||
return
|
||||
}
|
||||
executeEvent("selectedAppInfo", {
|
||||
data: app,
|
||||
});
|
||||
}}>
|
||||
<AppInfoAppName >
|
||||
{app?.metadata?.title || app?.name}
|
||||
</AppInfoAppName>
|
||||
</ButtonBase>
|
||||
<Spacer height="6px" />
|
||||
<AppInfoUserName>
|
||||
{ app?.name}
|
||||
</AppInfoUserName>
|
||||
<Spacer height="3px" />
|
||||
<AppRating app={app} myName={myName} />
|
||||
</AppInfoSnippetMiddle>
|
||||
</AppInfoSnippetLeft>
|
||||
<AppInfoSnippetRight sx={{
|
||||
gap: '10px'
|
||||
}}>
|
||||
{!isMobile && (
|
||||
<AppDownloadButton onClick={()=> {
|
||||
|
||||
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
|
||||
|
||||
}}>
|
||||
<AppDownloadButtonText> {isSelectedAppPinned ? 'Unpin' : 'Pin'}</AppDownloadButtonText>
|
||||
</AppDownloadButton>
|
||||
)}
|
||||
|
||||
<AppDownloadButton onClick={()=> {
|
||||
|
||||
executeEvent("addTab", {
|
||||
data: app
|
||||
})
|
||||
}} sx={{
|
||||
backgroundColor: isInstalled ? '#0091E1' : '#247C0E',
|
||||
|
||||
}}>
|
||||
<AppDownloadButtonText>{isInstalled ? 'Open' : 'Download'}</AppDownloadButtonText>
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor: isInstalled
|
||||
? '#0091E1'
|
||||
: theme.palette.background.paper,
|
||||
}}
|
||||
>
|
||||
<AppDownloadButtonText>
|
||||
{isInstalled ? 'Open' : 'Download'}
|
||||
</AppDownloadButtonText>
|
||||
</AppDownloadButton>
|
||||
</AppInfoSnippetRight>
|
||||
</AppInfoSnippetContainer>
|
||||
|
@ -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 (
|
||||
<AppsLibraryContainer sx={{
|
||||
height: !isMobile ? '100%' : 'auto',
|
||||
paddingTop: !isMobile && '30px',
|
||||
alignItems: !isMobile && 'center'
|
||||
}}>
|
||||
<AppsWidthLimiter sx={{
|
||||
width: !isMobile ? 'auto' : '90%'
|
||||
}}>
|
||||
<AppsLibraryContainer
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
paddingTop: '30px',
|
||||
}}
|
||||
>
|
||||
<AppsWidthLimiter
|
||||
sx={{
|
||||
width: 'auto',
|
||||
}}
|
||||
>
|
||||
<AppLibrarySubTitle>Create Apps!</AppLibrarySubTitle>
|
||||
|
||||
<Spacer height="18px" />
|
||||
|
||||
<PublishQAppInfo>
|
||||
Note: Currently, only one App and Website is allowed per Name.
|
||||
</PublishQAppInfo>
|
||||
|
||||
<Spacer height="18px" />
|
||||
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Name/App</InputLabel>
|
||||
|
||||
<InputLabel
|
||||
sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}
|
||||
>
|
||||
Name/App
|
||||
</InputLabel>
|
||||
|
||||
<CustomSelect
|
||||
placeholder="Select Name/App"
|
||||
displayEmpty
|
||||
@ -280,19 +278,26 @@ export const AppPublish = ({ names, categories }) => {
|
||||
<CustomMenuItem value="">
|
||||
<em
|
||||
style={{
|
||||
color: "var(--50-white, #FFFFFF80)",
|
||||
color: 'var(--50-white, #FFFFFF80)',
|
||||
}}
|
||||
>
|
||||
Select Name/App
|
||||
</em>{" "}
|
||||
</em>{' '}
|
||||
{/* This is the placeholder item */}
|
||||
</CustomMenuItem>
|
||||
{names.map((name) => {
|
||||
return <CustomMenuItem value={name}>{name}</CustomMenuItem>;
|
||||
})}
|
||||
</CustomSelect>
|
||||
|
||||
<Spacer height="15px" />
|
||||
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>App service type</InputLabel>
|
||||
|
||||
<InputLabel
|
||||
sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}
|
||||
>
|
||||
App service type
|
||||
</InputLabel>
|
||||
|
||||
<CustomSelect
|
||||
placeholder="SERVICE TYPE"
|
||||
displayEmpty
|
||||
@ -302,59 +307,79 @@ export const AppPublish = ({ names, categories }) => {
|
||||
<CustomMenuItem value="">
|
||||
<em
|
||||
style={{
|
||||
color: "var(--50-white, #FFFFFF80)",
|
||||
color: 'var(--50-white, #FFFFFF80)',
|
||||
}}
|
||||
>
|
||||
Select App Type
|
||||
</em>{" "}
|
||||
</em>{' '}
|
||||
{/* This is the placeholder item */}
|
||||
</CustomMenuItem>
|
||||
<CustomMenuItem value={"APP"}>App</CustomMenuItem>
|
||||
<CustomMenuItem value={"WEBSITE"}>Website</CustomMenuItem>
|
||||
<CustomMenuItem value={'APP'}>App</CustomMenuItem>
|
||||
<CustomMenuItem value={'WEBSITE'}>Website</CustomMenuItem>
|
||||
</CustomSelect>
|
||||
|
||||
<Spacer height="15px" />
|
||||
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Title</InputLabel>
|
||||
|
||||
<InputLabel
|
||||
sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}
|
||||
>
|
||||
Title
|
||||
</InputLabel>
|
||||
|
||||
<InputBase
|
||||
value={title}
|
||||
onChange={(e) => 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,
|
||||
}}
|
||||
/>
|
||||
<Spacer height="15px" />
|
||||
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Description</InputLabel>
|
||||
<InputBase
|
||||
value={description}
|
||||
onChange={(e) => 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,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Spacer height="15px" />
|
||||
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Category</InputLabel>
|
||||
|
||||
<InputLabel
|
||||
sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}
|
||||
>
|
||||
Description
|
||||
</InputLabel>
|
||||
|
||||
<InputBase
|
||||
value={description}
|
||||
onChange={(e) => 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,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Spacer height="15px" />
|
||||
|
||||
<InputLabel
|
||||
sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}
|
||||
>
|
||||
Category
|
||||
</InputLabel>
|
||||
|
||||
<CustomSelect
|
||||
displayEmpty
|
||||
placeholder="Select Category"
|
||||
@ -364,11 +389,11 @@ export const AppPublish = ({ names, categories }) => {
|
||||
<CustomMenuItem value="">
|
||||
<em
|
||||
style={{
|
||||
color: "var(--50-white, #FFFFFF80)",
|
||||
color: 'var(--50-white, #FFFFFF80)',
|
||||
}}
|
||||
>
|
||||
Select Category
|
||||
</em>{" "}
|
||||
</em>{' '}
|
||||
{/* This is the placeholder item */}
|
||||
</CustomMenuItem>
|
||||
{categories?.map((category) => {
|
||||
@ -379,23 +404,30 @@ export const AppPublish = ({ names, categories }) => {
|
||||
);
|
||||
})}
|
||||
</CustomSelect>
|
||||
|
||||
<Spacer height="15px" />
|
||||
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Tags</InputLabel>
|
||||
|
||||
<InputLabel
|
||||
sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}
|
||||
>
|
||||
Tags
|
||||
</InputLabel>
|
||||
|
||||
<AppPublishTagsContainer>
|
||||
<InputBase
|
||||
value={tag1}
|
||||
onChange={(e) => 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,
|
||||
}}
|
||||
/>
|
||||
</AppPublishTagsContainer>
|
||||
|
||||
<Spacer height="30px" />
|
||||
|
||||
<PublishQAppInfo>
|
||||
Select .zip file containing static content:{" "}
|
||||
Select .zip file containing static content:{' '}
|
||||
</PublishQAppInfo>
|
||||
|
||||
<Spacer height="10px" />
|
||||
|
||||
<PublishQAppInfo>{`(${
|
||||
appType === "APP" ? "50mb" : "400mb"
|
||||
appType === 'APP' ? '50mb' : '400mb'
|
||||
} MB maximum)`}</PublishQAppInfo>
|
||||
{file && (
|
||||
<>
|
||||
@ -484,21 +520,25 @@ export const AppPublish = ({ names, categories }) => {
|
||||
)}
|
||||
|
||||
<Spacer height="18px" />
|
||||
|
||||
<PublishQAppChoseFile {...getRootProps()}>
|
||||
{" "}
|
||||
{' '}
|
||||
<input {...getInputProps()} />
|
||||
Choose File
|
||||
</PublishQAppChoseFile>
|
||||
|
||||
<Spacer height="35px" />
|
||||
|
||||
<PublishQAppCTAButton
|
||||
sx={{
|
||||
alignSelf: "center",
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
onClick={publishApp}
|
||||
>
|
||||
Publish
|
||||
</PublishQAppCTAButton>
|
||||
</AppsWidthLimiter>
|
||||
|
||||
<LoadingSnackbar
|
||||
open={!!isLoading}
|
||||
info={{
|
||||
@ -512,7 +552,6 @@ export const AppPublish = ({ names, categories }) => {
|
||||
info={infoSnack}
|
||||
setInfo={setInfoSnack}
|
||||
/>
|
||||
|
||||
</AppsLibraryContainer>
|
||||
);
|
||||
};
|
||||
|
@ -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}`];
|
||||
|
@ -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 (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
style={{
|
||||
height: '100vh',
|
||||
border: 'none',
|
||||
width: '100%',
|
||||
}}
|
||||
id="browser-iframe"
|
||||
src={defaultUrl}
|
||||
sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-modals"
|
||||
allow="fullscreen; clipboard-read; clipboard-write"
|
||||
></iframe>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}>
|
||||
|
||||
<iframe ref={iframeRef} style={{
|
||||
height: !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px )`,
|
||||
border: 'none',
|
||||
width: '100%'
|
||||
}} id="browser-iframe" src={defaultUrl} sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-modals"
|
||||
allow="fullscreen; clipboard-read; clipboard-write">
|
||||
|
||||
</iframe>
|
||||
</Box>
|
||||
|
||||
);
|
||||
});
|
||||
);
|
||||
|
@ -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 (
|
||||
<Frame
|
||||
id={`browser-iframe-${app?.tabId}`}
|
||||
|
||||
head={
|
||||
<>
|
||||
<style>
|
||||
{`
|
||||
return (
|
||||
<Frame
|
||||
id={`browser-iframe-${app?.tabId}`}
|
||||
head={
|
||||
<>
|
||||
<style>
|
||||
{`
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
* {
|
||||
-msOverflowStyle: none; /* IE and Edge */
|
||||
msOverflowStyle: 'none', /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
*::-webkit-scrollbar {
|
||||
@ -28,24 +27,31 @@ const AppViewerContainer = React.forwardRef(({ app, isSelected, hide, isDevMode,
|
||||
}
|
||||
.frame-content {
|
||||
overflow: hidden;
|
||||
height: ${!isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px)`};
|
||||
height: '100vh';
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</>
|
||||
}
|
||||
style={{
|
||||
position: (!isSelected || hide) && 'fixed',
|
||||
left: (!isSelected || hide) && '-200vw',
|
||||
height: customHeight ? customHeight : !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px)`,
|
||||
border: 'none',
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<AppViewer skipAuth={skipAuth} app={app} ref={ref} hide={!isSelected || hide} isDevMode={isDevMode} />
|
||||
</Frame>
|
||||
);
|
||||
});
|
||||
</style>
|
||||
</>
|
||||
}
|
||||
style={{
|
||||
border: 'none',
|
||||
height: '100vh',
|
||||
left: (!isSelected || hide) && '-200vw',
|
||||
overflow: 'hidden',
|
||||
position: (!isSelected || hide) && 'fixed',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<AppViewer
|
||||
skipAuth={skipAuth}
|
||||
app={app}
|
||||
ref={ref}
|
||||
hide={!isSelected || hide}
|
||||
isDevMode={isDevMode}
|
||||
/>
|
||||
</Frame>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default AppViewerContainer;
|
||||
|
@ -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 }) => ({
|
||||
|
@ -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 (
|
||||
<AppsParent
|
||||
sx={{
|
||||
display: !show && 'none',
|
||||
}}
|
||||
>
|
||||
{mode !== 'viewer' && !selectedTab && <Spacer height="30px" />}
|
||||
{mode === 'home' && (
|
||||
<AppsHome
|
||||
availableQapps={availableQapps}
|
||||
setMode={setMode}
|
||||
myApp={myApp}
|
||||
myWebsite={myWebsite}
|
||||
/>
|
||||
)}
|
||||
|
||||
<AppsLibrary
|
||||
isShow={mode === 'library' && !selectedTab}
|
||||
availableQapps={availableQapps}
|
||||
setMode={setMode}
|
||||
myName={myName}
|
||||
hasPublishApp={!!(myApp || myWebsite)}
|
||||
categories={categories}
|
||||
/>
|
||||
|
||||
{mode === 'appInfo' && !selectedTab && (
|
||||
<AppInfo app={selectedAppInfo} myName={myName} />
|
||||
)}
|
||||
{mode === 'appInfo-from-category' && !selectedTab && (
|
||||
<AppInfo app={selectedAppInfo} myName={myName} />
|
||||
)}
|
||||
<AppsCategory
|
||||
availableQapps={availableQapps}
|
||||
isShow={mode === 'category' && !selectedTab}
|
||||
category={selectedCategory}
|
||||
myName={myName}
|
||||
/>
|
||||
{mode === 'publish' && !selectedTab && (
|
||||
<AppPublish names={myName ? [myName] : []} categories={categories} />
|
||||
)}
|
||||
|
||||
{tabs.map((tab) => {
|
||||
if (!iframeRefs.current[tab.tabId]) {
|
||||
iframeRefs.current[tab.tabId] = React.createRef();
|
||||
}
|
||||
return (
|
||||
<AppViewerContainer
|
||||
key={tab?.tabId}
|
||||
hide={isNewTabWindow}
|
||||
isSelected={tab?.tabId === selectedTab?.tabId}
|
||||
app={tab}
|
||||
ref={iframeRefs.current[tab.tabId]}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{isNewTabWindow && mode === 'viewer' && (
|
||||
<>
|
||||
<Spacer height="30px" />
|
||||
<AppsHome
|
||||
availableQapps={availableQapps}
|
||||
setMode={setMode}
|
||||
myApp={myApp}
|
||||
myWebsite={myWebsite}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{mode !== 'viewer' && !selectedTab && <Spacer height="180px" />}
|
||||
</AppsParent>
|
||||
);
|
||||
};
|
@ -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 <AppInfoSnippet key={`${app?.service}-${app?.name}`} app={app} myName={myName} isFromCategory={true} />;
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<AppsLibraryContainer sx={{
|
||||
display: !isShow && 'none'
|
||||
}}>
|
||||
<AppsWidthLimiter>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<AppsSearchContainer>
|
||||
<AppsSearchLeft>
|
||||
<img src={IconSearch} />
|
||||
<InputBase
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
sx={{ ml: 1, flex: 1 }}
|
||||
placeholder="Search for apps"
|
||||
inputProps={{
|
||||
"aria-label": "Search for apps",
|
||||
fontSize: "16px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
/>
|
||||
</AppsSearchLeft>
|
||||
<AppsSearchRight>
|
||||
{searchValue && (
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setSearchValue("");
|
||||
}}
|
||||
>
|
||||
<img src={IconClearInput} />
|
||||
</ButtonBase>
|
||||
)}
|
||||
</AppsSearchRight>
|
||||
</AppsSearchContainer>
|
||||
</Box>
|
||||
</AppsWidthLimiter>
|
||||
<Spacer height="25px" />
|
||||
<AppsWidthLimiter>
|
||||
<AppLibrarySubTitle>{`Category: ${category?.name}`}</AppLibrarySubTitle>
|
||||
|
||||
<Spacer height="25px" />
|
||||
</AppsWidthLimiter>
|
||||
<AppsWidthLimiter>
|
||||
<StyledVirtuosoContainer sx={{
|
||||
height: rootHeight
|
||||
}}>
|
||||
<Virtuoso
|
||||
ref={virtuosoRef}
|
||||
data={searchedList}
|
||||
itemContent={rowRenderer}
|
||||
atBottomThreshold={50}
|
||||
followOutput="smooth"
|
||||
components={{
|
||||
Scroller: ScrollerStyled // Use the styled scroller component
|
||||
}}
|
||||
/>
|
||||
</StyledVirtuosoContainer>
|
||||
</AppsWidthLimiter>
|
||||
|
||||
</AppsLibraryContainer>
|
||||
);
|
||||
};
|
@ -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 = ({
|
||||
<AppsLibraryContainer
|
||||
sx={{
|
||||
display: !isShow && 'none',
|
||||
padding: '0px',
|
||||
height: '100vh',
|
||||
overflow: 'hidden',
|
||||
padding: '0px',
|
||||
paddingTop: '30px',
|
||||
}}
|
||||
>
|
||||
<AppsDesktopLibraryHeader
|
||||
sx={{
|
||||
maxWidth: '1500px',
|
||||
maxWidth: '1200px',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
@ -181,11 +142,18 @@ export const AppsCategoryDesktop = ({
|
||||
}}
|
||||
>
|
||||
<AppsSearchLeft>
|
||||
<img src={IconSearch} />
|
||||
<SearchIcon />
|
||||
|
||||
<InputBase
|
||||
value={searchValue}
|
||||
onChange={(e) => 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 = ({
|
||||
}}
|
||||
/>
|
||||
</AppsSearchLeft>
|
||||
|
||||
<AppsSearchRight>
|
||||
{searchValue && (
|
||||
<ButtonBase
|
||||
@ -208,20 +177,24 @@ export const AppsCategoryDesktop = ({
|
||||
</AppsSearchContainer>
|
||||
</AppsWidthLimiter>
|
||||
</AppsDesktopLibraryHeader>
|
||||
|
||||
<AppsDesktopLibraryBody
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
height: `calc(100vh - 36px)`,
|
||||
overflow: 'auto',
|
||||
padding: '0px',
|
||||
alignItems: 'center',
|
||||
width: '70%',
|
||||
}}
|
||||
>
|
||||
<Spacer height="25px" />
|
||||
|
||||
<AppsWidthLimiter>
|
||||
<AppLibrarySubTitle>{`Category: ${category?.name}`}</AppLibrarySubTitle>
|
||||
|
||||
<Spacer height="25px" />
|
||||
</AppsWidthLimiter>
|
||||
|
||||
<AppsWidthLimiter>
|
||||
<StyledVirtuosoContainer
|
||||
sx={{
|
||||
|
@ -1,24 +0,0 @@
|
||||
import {
|
||||
AppBar,
|
||||
Button,
|
||||
Toolbar,
|
||||
Typography,
|
||||
Box,
|
||||
TextField,
|
||||
InputLabel,
|
||||
ButtonBase,
|
||||
} from "@mui/material";
|
||||
import { styled } from "@mui/system";
|
||||
|
||||
export const AppsDesktopLibraryHeader = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
flexDirection: 'column',
|
||||
flexShrink: 0,
|
||||
width: '100%'
|
||||
}));
|
||||
export const AppsDesktopLibraryBody = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
width: '100%'
|
||||
}));
|
@ -478,13 +478,13 @@ export const AppsDesktop = ({
|
||||
)}
|
||||
|
||||
<AppsLibraryDesktop
|
||||
isShow={mode === 'library' && !selectedTab}
|
||||
availableQapps={availableQapps}
|
||||
setMode={setMode}
|
||||
myName={myName}
|
||||
hasPublishApp={!!(myApp || myWebsite)}
|
||||
categories={categories}
|
||||
getQapps={getQapps}
|
||||
hasPublishApp={!!(myApp || myWebsite)}
|
||||
isShow={mode === 'library' && !selectedTab}
|
||||
myName={myName}
|
||||
setMode={setMode}
|
||||
/>
|
||||
|
||||
{mode === 'appInfo' && !selectedTab && (
|
||||
|
@ -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
|
||||
</AppLibrarySubTitle>
|
||||
</AppsContainer>
|
||||
|
||||
<Spacer height="45px" />
|
||||
|
||||
<AppsContainer
|
||||
sx={{
|
||||
gap: '75px',
|
||||
@ -293,7 +295,7 @@ export const AppsDevModeHome = ({
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? '10px' : '5px',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
@ -302,6 +304,7 @@ export const AppsDevModeHome = ({
|
||||
<AppCircleLabel>Server</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
addPreviewApp();
|
||||
@ -309,15 +312,17 @@ export const AppsDevModeHome = ({
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? '10px' : '5px',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
<Add>+</Add>
|
||||
</AppCircle>
|
||||
|
||||
<AppCircleLabel>Zip</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
addPreviewAppWithDirectory();
|
||||
@ -325,7 +330,7 @@ export const AppsDevModeHome = ({
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? '10px' : '5px',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
@ -334,6 +339,7 @@ export const AppsDevModeHome = ({
|
||||
<AppCircleLabel>Directory</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
executeEvent('appsDevModeAddTab', {
|
||||
@ -347,7 +353,7 @@ export const AppsDevModeHome = ({
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? '10px' : '5px',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
@ -371,9 +377,11 @@ export const AppsDevModeHome = ({
|
||||
/>
|
||||
</Avatar>
|
||||
</AppCircle>
|
||||
|
||||
<AppCircleLabel>Q-Sandbox</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
executeEvent('appsDevModeAddTab', {
|
||||
@ -387,7 +395,7 @@ export const AppsDevModeHome = ({
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? '10px' : '5px',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
@ -411,10 +419,12 @@ export const AppsDevModeHome = ({
|
||||
/>
|
||||
</Avatar>
|
||||
</AppCircle>
|
||||
|
||||
<AppCircleLabel>API</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
</AppsContainer>
|
||||
|
||||
{isShow && (
|
||||
<Dialog
|
||||
open={isShow}
|
||||
@ -429,6 +439,7 @@ export const AppsDevModeHome = ({
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{'Add custom framework'}
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
<Box
|
||||
sx={{
|
||||
@ -460,6 +471,7 @@ export const AppsDevModeHome = ({
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button variant="contained" onClick={onCancel}>
|
||||
Close
|
||||
|
@ -1,56 +0,0 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
AppCircleLabel,
|
||||
AppLibrarySubTitle,
|
||||
AppsContainer,
|
||||
AppsParent,
|
||||
} from './Apps-styles';
|
||||
import { Avatar, ButtonBase } from '@mui/material';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { getBaseApiReact, isMobile } from '../../App';
|
||||
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
|
||||
import { executeEvent } from '../../utils/events';
|
||||
import { SortablePinnedApps } from './SortablePinnedApps';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
|
||||
export const AppsHome = ({ setMode, myApp, myWebsite, availableQapps }) => {
|
||||
return (
|
||||
<>
|
||||
<AppsContainer
|
||||
sx={{
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<AppLibrarySubTitle>Apps Dashboard</AppLibrarySubTitle>
|
||||
</AppsContainer>
|
||||
<Spacer height="20px" />
|
||||
|
||||
<AppsContainer>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setMode('library');
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? '10px' : '5px',
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
<Add>+</Add>
|
||||
</AppCircle>
|
||||
<AppCircleLabel>Library</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
|
||||
<SortablePinnedApps
|
||||
availableQapps={availableQapps}
|
||||
myWebsite={myWebsite}
|
||||
myApp={myApp}
|
||||
/>
|
||||
</AppsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
@ -7,8 +7,7 @@ import {
|
||||
AppsContainer,
|
||||
} from './Apps-styles';
|
||||
import { Box, ButtonBase, Input, useTheme } from '@mui/material';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { isMobile } from '../../App';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import { executeEvent } from '../../utils/events';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import { SortablePinnedApps } from './SortablePinnedApps';
|
||||
@ -137,12 +136,13 @@ export const AppsHomeDesktop = ({
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? '10px' : '5px',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
<Add>+</Add>
|
||||
<AddIcon />
|
||||
</AppCircle>
|
||||
|
||||
<AppCircleLabel>Library</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
|
@ -1,384 +0,0 @@
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
AppCircleLabel,
|
||||
AppLibrarySubTitle,
|
||||
AppsContainer,
|
||||
AppsLibraryContainer,
|
||||
AppsSearchContainer,
|
||||
AppsSearchLeft,
|
||||
AppsSearchRight,
|
||||
AppsWidthLimiter,
|
||||
PublishQAppCTAButton,
|
||||
PublishQAppCTALeft,
|
||||
PublishQAppCTAParent,
|
||||
PublishQAppCTARight,
|
||||
PublishQAppDotsBG,
|
||||
} from './Apps-styles';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
ButtonBase,
|
||||
InputBase,
|
||||
styled,
|
||||
useTheme,
|
||||
} from '@mui/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 { Return } from './assets/svgs/Return.tsx';
|
||||
import ReturnSVG from '../../assets/svgs/Return.svg';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import { AppInfoSnippet } from './AppInfoSnippet';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
import { executeEvent } from '../../utils/events';
|
||||
import {
|
||||
ComposeP,
|
||||
MailIconImg,
|
||||
ShowMessageReturnButton,
|
||||
} from '../Group/Forum/Mail-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)
|
||||
'::-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')({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
|
||||
// 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 AppsLibrary = ({
|
||||
availableQapps,
|
||||
setMode,
|
||||
myName,
|
||||
hasPublishApp,
|
||||
isShow,
|
||||
categories = { categories },
|
||||
}) => {
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const virtuosoRef = useRef();
|
||||
const { rootHeight } = useContext(MyContext);
|
||||
|
||||
const officialApps = useMemo(() => {
|
||||
return availableQapps.filter(
|
||||
(app) =>
|
||||
app.service === 'APP' &&
|
||||
officialAppList.includes(app?.name?.toLowerCase())
|
||||
);
|
||||
}, [availableQapps]);
|
||||
|
||||
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 [];
|
||||
return availableQapps.filter((app) =>
|
||||
app.name.toLowerCase().includes(debouncedValue.toLowerCase())
|
||||
);
|
||||
}, [debouncedValue]);
|
||||
|
||||
const rowRenderer = (index) => {
|
||||
let app = searchedList[index];
|
||||
return (
|
||||
<AppInfoSnippet
|
||||
key={`${app?.service}-${app?.name}`}
|
||||
app={app}
|
||||
myName={myName}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<AppsLibraryContainer
|
||||
sx={{
|
||||
display: !isShow && 'none',
|
||||
}}
|
||||
>
|
||||
<AppsWidthLimiter>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<AppsSearchContainer>
|
||||
<AppsSearchLeft>
|
||||
<img src={IconSearch} />
|
||||
|
||||
<InputBase
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
sx={{ ml: 1, flex: 1 }}
|
||||
placeholder="Search for apps"
|
||||
inputProps={{
|
||||
'aria-label': 'Search for apps',
|
||||
fontSize: '16px',
|
||||
fontWeight: 400,
|
||||
}}
|
||||
/>
|
||||
</AppsSearchLeft>
|
||||
<AppsSearchRight>
|
||||
{searchValue && (
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setSearchValue('');
|
||||
}}
|
||||
>
|
||||
<img src={IconClearInput} />
|
||||
</ButtonBase>
|
||||
)}
|
||||
</AppsSearchRight>
|
||||
</AppsSearchContainer>
|
||||
</Box>
|
||||
</AppsWidthLimiter>
|
||||
|
||||
<Spacer height="25px" />
|
||||
|
||||
<ShowMessageReturnButton
|
||||
sx={{
|
||||
padding: '2px',
|
||||
}}
|
||||
onClick={() => {
|
||||
executeEvent('navigateBack', {});
|
||||
}}
|
||||
>
|
||||
<MailIconImg src={ReturnSVG} /> // TODO return icon
|
||||
<ComposeP>Return to Apps Dashboard</ComposeP>
|
||||
</ShowMessageReturnButton>
|
||||
|
||||
<Spacer height="25px" />
|
||||
|
||||
{searchedList?.length > 0 ? (
|
||||
<AppsWidthLimiter>
|
||||
<StyledVirtuosoContainer
|
||||
sx={{
|
||||
height: rootHeight,
|
||||
}}
|
||||
>
|
||||
<Virtuoso
|
||||
ref={virtuosoRef}
|
||||
data={searchedList}
|
||||
itemContent={rowRenderer}
|
||||
atBottomThreshold={50}
|
||||
followOutput="smooth"
|
||||
components={{
|
||||
Scroller: ScrollerStyled, // Use the styled scroller component
|
||||
}}
|
||||
/>
|
||||
</StyledVirtuosoContainer>
|
||||
</AppsWidthLimiter>
|
||||
) : (
|
||||
<>
|
||||
<AppsWidthLimiter>
|
||||
<AppLibrarySubTitle>Official Apps</AppLibrarySubTitle>
|
||||
|
||||
<Spacer height="18px" />
|
||||
|
||||
<AppsContainer>
|
||||
{officialApps?.map((qapp) => {
|
||||
return (
|
||||
<ButtonBase
|
||||
sx={{
|
||||
height: '80px',
|
||||
width: '60px',
|
||||
}}
|
||||
onClick={() => {
|
||||
// executeEvent("addTab", {
|
||||
// data: qapp
|
||||
// })
|
||||
executeEvent('selectedAppInfo', {
|
||||
data: qapp,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer>
|
||||
<AppCircle
|
||||
sx={{
|
||||
border: 'none',
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
height: '31px',
|
||||
width: '31px',
|
||||
}}
|
||||
alt={qapp?.name}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
qapp?.name
|
||||
}/qortal_avatar?async=true`}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '31px',
|
||||
height: 'auto',
|
||||
}}
|
||||
src={LogoSelected}
|
||||
alt="center-icon"
|
||||
/>
|
||||
</Avatar>
|
||||
</AppCircle>
|
||||
|
||||
<AppCircleLabel>
|
||||
{qapp?.metadata?.title || qapp?.name}
|
||||
</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
);
|
||||
})}
|
||||
</AppsContainer>
|
||||
|
||||
<Spacer height="30px" />
|
||||
|
||||
<AppLibrarySubTitle>
|
||||
{hasPublishApp ? 'Update Apps!' : 'Create Apps!'}
|
||||
</AppLibrarySubTitle>
|
||||
|
||||
<Spacer height="18px" />
|
||||
</AppsWidthLimiter>
|
||||
|
||||
<PublishQAppCTAParent>
|
||||
<PublishQAppCTALeft>
|
||||
<PublishQAppDotsBG>
|
||||
<img src={qappDots} />
|
||||
</PublishQAppDotsBG>
|
||||
|
||||
<Spacer width="29px" />
|
||||
|
||||
<img src={qappDevelopText} />
|
||||
</PublishQAppCTALeft>
|
||||
|
||||
<PublishQAppCTARight
|
||||
onClick={() => {
|
||||
setMode('publish');
|
||||
}}
|
||||
>
|
||||
<PublishQAppCTAButton>
|
||||
{hasPublishApp ? 'Update' : 'Publish'}
|
||||
</PublishQAppCTAButton>
|
||||
|
||||
<Spacer width="20px" />
|
||||
</PublishQAppCTARight>
|
||||
</PublishQAppCTAParent>
|
||||
|
||||
<AppsWidthLimiter>
|
||||
<Spacer height="18px" />
|
||||
|
||||
<AppLibrarySubTitle>Categories</AppLibrarySubTitle>
|
||||
|
||||
<Spacer height="18px" />
|
||||
|
||||
<AppsWidthLimiter
|
||||
sx={{
|
||||
flexDirection: 'row',
|
||||
overflowX: 'auto',
|
||||
width: '100%',
|
||||
gap: '5px',
|
||||
'::-webkit-scrollbar': {
|
||||
width: '0px',
|
||||
height: '0px',
|
||||
},
|
||||
|
||||
// Hide scrollbar for Firefox
|
||||
scrollbarWidth: 'none',
|
||||
|
||||
// Hide scrollbar for IE and older Edge
|
||||
'-msOverflowStyle': 'none',
|
||||
}}
|
||||
>
|
||||
{categories?.map((category) => {
|
||||
return (
|
||||
<ButtonBase
|
||||
key={category?.id}
|
||||
onClick={() => {
|
||||
executeEvent('selectedCategory', {
|
||||
data: category,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
background: theme.palette.background.default,
|
||||
borderRadius: '11px',
|
||||
color: theme.palette.text.primary,
|
||||
display: 'flex',
|
||||
flexShrink: 0,
|
||||
fontSize: '16px',
|
||||
fontWeight: 700,
|
||||
height: '110px',
|
||||
justifyContent: 'center',
|
||||
width: '110px',
|
||||
}}
|
||||
>
|
||||
{category?.name}
|
||||
</Box>
|
||||
</ButtonBase>
|
||||
);
|
||||
})}
|
||||
</AppsWidthLimiter>
|
||||
</AppsWidthLimiter>
|
||||
</>
|
||||
)}
|
||||
</AppsLibraryContainer>
|
||||
);
|
||||
};
|
@ -1,19 +1,13 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
AppCircleLabel,
|
||||
AppLibrarySubTitle,
|
||||
AppsContainer,
|
||||
AppsDesktopLibraryBody,
|
||||
AppsDesktopLibraryHeader,
|
||||
AppsLibraryContainer,
|
||||
AppsParent,
|
||||
AppsSearchContainer,
|
||||
AppsSearchLeft,
|
||||
AppsSearchRight,
|
||||
@ -31,32 +25,22 @@ import {
|
||||
InputBase,
|
||||
Typography,
|
||||
styled,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { MyContext, getBaseApiReact } from '../../App';
|
||||
import { getBaseApiReact } from '../../App';
|
||||
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
|
||||
import IconSearch from '../../assets/svgs/Search.svg';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import IconClearInput from '../../assets/svgs/ClearInput.svg';
|
||||
import qappDevelopText from '../../assets/svgs/qappDevelopText.svg';
|
||||
import qappLibraryText from '../../assets/svgs/qappLibraryText.svg';
|
||||
import { QappDevelopText } from '../../assets/Icons/QappDevelopText.tsx';
|
||||
import { QappLibraryText } from '../../assets/Icons/QappLibraryText.tsx';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
|
||||
import qappDots from '../../assets/svgs/qappDots.svg';
|
||||
|
||||
import AppsIcon from '@mui/icons-material/Apps';
|
||||
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';
|
||||
import ReturnSVG from '../../assets/svgs/Return.svg';
|
||||
import {
|
||||
ComposeP,
|
||||
MailIconImg,
|
||||
ShowMessageReturnButton,
|
||||
} from '../Group/Forum/Mail-styles';
|
||||
import { ShowMessageReturnButton } from '../Group/Forum/Mail-styles';
|
||||
|
||||
const officialAppList = [
|
||||
'q-tube',
|
||||
'q-blog',
|
||||
@ -85,7 +69,7 @@ const ScrollerStyled = styled('div')({
|
||||
scrollbarWidth: 'none',
|
||||
|
||||
// Hide scrollbar for IE and older Edge
|
||||
'-msOverflowStyle': 'none',
|
||||
msOverflowStyle: 'none',
|
||||
});
|
||||
|
||||
const StyledVirtuosoContainer = styled('div')({
|
||||
@ -104,7 +88,7 @@ const StyledVirtuosoContainer = styled('div')({
|
||||
scrollbarWidth: 'none',
|
||||
|
||||
// Hide scrollbar for IE and older Edge
|
||||
'-msOverflowStyle': 'none',
|
||||
msOverflowStyle: 'none',
|
||||
});
|
||||
|
||||
export const AppsLibraryDesktop = ({
|
||||
@ -118,6 +102,7 @@ export const AppsLibraryDesktop = ({
|
||||
}) => {
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const virtuosoRef = useRef();
|
||||
const theme = useTheme();
|
||||
|
||||
const officialApps = useMemo(() => {
|
||||
return availableQapps.filter(
|
||||
@ -192,17 +177,18 @@ export const AppsLibraryDesktop = ({
|
||||
<AppsWidthLimiter>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<img src={qappLibraryText} />
|
||||
<QappLibraryText />
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
gap: '20px',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<AppsSearchContainer
|
||||
@ -211,11 +197,18 @@ export const AppsLibraryDesktop = ({
|
||||
}}
|
||||
>
|
||||
<AppsSearchLeft>
|
||||
<img src={IconSearch} />
|
||||
<SearchIcon />
|
||||
|
||||
<InputBase
|
||||
value={searchValue}
|
||||
onChange={(e) => 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',
|
||||
@ -224,6 +217,7 @@ export const AppsLibraryDesktop = ({
|
||||
}}
|
||||
/>
|
||||
</AppsSearchLeft>
|
||||
|
||||
<AppsSearchRight>
|
||||
{searchValue && (
|
||||
<ButtonBase
|
||||
@ -236,6 +230,7 @@ export const AppsLibraryDesktop = ({
|
||||
)}
|
||||
</AppsSearchRight>
|
||||
</AppsSearchContainer>
|
||||
|
||||
<ButtonBase
|
||||
onClick={(e) => {
|
||||
getQapps();
|
||||
@ -243,7 +238,6 @@ export const AppsLibraryDesktop = ({
|
||||
>
|
||||
<RefreshIcon
|
||||
sx={{
|
||||
color: 'rgba(250, 250, 250, 0.5)',
|
||||
width: '40px',
|
||||
height: 'auto',
|
||||
}}
|
||||
@ -253,12 +247,13 @@ export const AppsLibraryDesktop = ({
|
||||
</Box>
|
||||
</AppsWidthLimiter>
|
||||
</AppsDesktopLibraryHeader>
|
||||
|
||||
<AppsDesktopLibraryBody
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
height: `calc(100vh - 36px)`,
|
||||
overflow: 'auto',
|
||||
padding: '0px',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<AppsDesktopLibraryBody
|
||||
@ -270,6 +265,7 @@ export const AppsLibraryDesktop = ({
|
||||
}}
|
||||
>
|
||||
<Spacer height="70px" />
|
||||
|
||||
<ShowMessageReturnButton
|
||||
sx={{
|
||||
padding: '2px',
|
||||
@ -277,11 +273,10 @@ export const AppsLibraryDesktop = ({
|
||||
onClick={() => {
|
||||
executeEvent('navigateBack', {});
|
||||
}}
|
||||
>
|
||||
<MailIconImg src={ReturnSVG} />
|
||||
<ComposeP>Return to Apps Dashboard</ComposeP>
|
||||
</ShowMessageReturnButton>
|
||||
></ShowMessageReturnButton>
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
||||
{searchedList?.length > 0 ? (
|
||||
<AppsWidthLimiter>
|
||||
<StyledVirtuosoContainer
|
||||
@ -314,24 +309,22 @@ export const AppsLibraryDesktop = ({
|
||||
>
|
||||
Official Apps
|
||||
</AppLibrarySubTitle>
|
||||
|
||||
<Spacer height="45px" />
|
||||
|
||||
<AppsContainer
|
||||
sx={{
|
||||
gap: '50px',
|
||||
justifyContent: 'flex-start',
|
||||
gap: '15px',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{officialApps?.map((qapp) => {
|
||||
return (
|
||||
<ButtonBase
|
||||
sx={{
|
||||
// height: "80px",
|
||||
width: '80px',
|
||||
}}
|
||||
onClick={() => {
|
||||
// executeEvent("addTab", {
|
||||
// data: qapp
|
||||
// })
|
||||
executeEvent('selectedAppInfo', {
|
||||
data: qapp,
|
||||
});
|
||||
@ -367,6 +360,7 @@ export const AppsLibraryDesktop = ({
|
||||
/>
|
||||
</Avatar>
|
||||
</AppCircle>
|
||||
|
||||
<AppCircleLabel>
|
||||
{qapp?.metadata?.title || qapp?.name}
|
||||
</AppCircleLabel>
|
||||
@ -375,7 +369,9 @@ export const AppsLibraryDesktop = ({
|
||||
);
|
||||
})}
|
||||
</AppsContainer>
|
||||
|
||||
<Spacer height="80px" />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
@ -396,9 +392,11 @@ export const AppsLibraryDesktop = ({
|
||||
textAlign: 'start',
|
||||
}}
|
||||
>
|
||||
{hasPublishApp ? 'Update Apps!' : 'Create Apps!'}
|
||||
{hasPublishApp ? 'Update your app' : 'Publish your app'}
|
||||
</AppLibrarySubTitle>
|
||||
|
||||
<Spacer height="18px" />
|
||||
|
||||
<PublishQAppCTAParent
|
||||
sx={{
|
||||
gap: '25px',
|
||||
@ -406,11 +404,14 @@ export const AppsLibraryDesktop = ({
|
||||
>
|
||||
<PublishQAppCTALeft>
|
||||
<PublishQAppDotsBG>
|
||||
<img src={qappDots} />
|
||||
<AppsIcon fontSize="large" />
|
||||
</PublishQAppDotsBG>
|
||||
|
||||
<Spacer width="29px" />
|
||||
<img src={qappDevelopText} />
|
||||
|
||||
<QappDevelopText />
|
||||
</PublishQAppCTALeft>
|
||||
|
||||
<PublishQAppCTARight
|
||||
onClick={() => {
|
||||
setMode('publish');
|
||||
@ -419,10 +420,12 @@ export const AppsLibraryDesktop = ({
|
||||
<PublishQAppCTAButton>
|
||||
{hasPublishApp ? 'Update' : 'Publish'}
|
||||
</PublishQAppCTAButton>
|
||||
|
||||
<Spacer width="20px" />
|
||||
</PublishQAppCTARight>
|
||||
</PublishQAppCTAParent>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@ -436,13 +439,15 @@ export const AppsLibraryDesktop = ({
|
||||
>
|
||||
Categories
|
||||
</AppLibrarySubTitle>
|
||||
|
||||
<Spacer height="18px" />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
gap: '20px',
|
||||
flexWrap: 'wrap',
|
||||
gap: '20px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
@ -457,19 +462,22 @@ export const AppsLibraryDesktop = ({
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '60px',
|
||||
padding: '0px 24px',
|
||||
border: '4px solid #10242F',
|
||||
borderColor: theme.palette.background.paper,
|
||||
borderRadius: '6px',
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '4px',
|
||||
boxShadow: '2px 4px 0px 0px #000000',
|
||||
display: 'flex',
|
||||
height: '50px',
|
||||
justifyContent: 'center',
|
||||
padding: '0px 20px',
|
||||
}}
|
||||
>
|
||||
All
|
||||
</Box>
|
||||
</ButtonBase>
|
||||
|
||||
{categories?.map((category) => {
|
||||
return (
|
||||
<ButtonBase
|
||||
@ -482,14 +490,16 @@ export const AppsLibraryDesktop = ({
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '60px',
|
||||
padding: '0px 24px',
|
||||
border: '4px solid #10242F',
|
||||
borderColor: theme.palette.background.paper,
|
||||
borderRadius: '6px',
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '4px',
|
||||
boxShadow: '2px 4px 0px 0px #000000',
|
||||
display: 'flex',
|
||||
height: '50px',
|
||||
justifyContent: 'center',
|
||||
padding: '0px 20px',
|
||||
}}
|
||||
>
|
||||
{category?.name}
|
||||
|
@ -1,367 +0,0 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
AppsNavBarLeft,
|
||||
AppsNavBarParent,
|
||||
AppsNavBarRight,
|
||||
} from './Apps-styles';
|
||||
import { NavBack } from '../../assets/Icons/NavBack.tsx';
|
||||
import { NavAdd } from '../../assets/Icons/NavAdd.tsx';
|
||||
import { NavMoreMenu } from '../../assets/Icons/NavMoreMenu.tsx';
|
||||
import {
|
||||
ButtonBase,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Tab,
|
||||
Tabs,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
executeEvent,
|
||||
subscribeToEvent,
|
||||
unsubscribeFromEvent,
|
||||
} from '../../utils/events';
|
||||
import TabComponent from './TabComponent';
|
||||
import PushPinIcon from '@mui/icons-material/PushPin';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import {
|
||||
navigationControllerAtom,
|
||||
settingsLocalLastUpdatedAtom,
|
||||
sortablePinnedAppsAtom,
|
||||
} from '../../atoms/global';
|
||||
|
||||
export function saveToLocalStorage(
|
||||
key,
|
||||
subKey,
|
||||
newValue,
|
||||
otherRootData = {},
|
||||
deleteWholeKey
|
||||
) {
|
||||
try {
|
||||
if (deleteWholeKey) {
|
||||
localStorage.setItem(key, null);
|
||||
return;
|
||||
}
|
||||
// Fetch existing data
|
||||
const existingData = localStorage.getItem(key);
|
||||
let combinedData = {};
|
||||
|
||||
if (existingData) {
|
||||
// Parse the existing data
|
||||
const parsedData = JSON.parse(existingData);
|
||||
// Merge with the new data under the subKey
|
||||
combinedData = {
|
||||
...parsedData,
|
||||
...otherRootData,
|
||||
timestamp: Date.now(), // Update the root timestamp
|
||||
[subKey]: newValue, // Assuming the data is an array
|
||||
};
|
||||
} else {
|
||||
// If no existing data, just use the new data under the subKey
|
||||
combinedData = {
|
||||
...otherRootData,
|
||||
timestamp: Date.now(), // Set the initial root timestamp
|
||||
[subKey]: newValue,
|
||||
};
|
||||
}
|
||||
|
||||
// Save combined data back to localStorage
|
||||
const serializedValue = JSON.stringify(combinedData);
|
||||
localStorage.setItem(key, serializedValue);
|
||||
} catch (error) {
|
||||
console.error('Error saving to localStorage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export const AppsNavBar = () => {
|
||||
const [tabs, setTabs] = useState([]);
|
||||
const [selectedTab, setSelectedTab] = useState(null);
|
||||
const [isNewTabWindow, setIsNewTabWindow] = useState(false);
|
||||
const tabsRef = useRef(null);
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(
|
||||
sortablePinnedAppsAtom
|
||||
);
|
||||
const [navigationController, setNavigationController] = useRecoilState(
|
||||
navigationControllerAtom
|
||||
);
|
||||
|
||||
const isDisableBackButton = useMemo(() => {
|
||||
if (selectedTab && navigationController[selectedTab?.tabId]?.hasBack)
|
||||
return false;
|
||||
if (selectedTab && !navigationController[selectedTab?.tabId]?.hasBack)
|
||||
return true;
|
||||
return false;
|
||||
}, [navigationController, selectedTab]);
|
||||
|
||||
const setSettingsLocalLastUpdated = useSetRecoilState(
|
||||
settingsLocalLastUpdatedAtom
|
||||
);
|
||||
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Scroll to the last tab whenever the tabs array changes (e.g., when a new tab is added)
|
||||
if (tabsRef.current) {
|
||||
const tabElements = tabsRef.current.querySelectorAll('.MuiTab-root');
|
||||
if (tabElements.length > 0) {
|
||||
const lastTab = tabElements[tabElements.length - 1];
|
||||
lastTab.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'end',
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [tabs.length]); // Dependency on the number of tabs
|
||||
|
||||
const setTabsToNav = (e) => {
|
||||
const { tabs, selectedTab, isNewTabWindow } = e.detail?.data;
|
||||
|
||||
setTabs([...tabs]);
|
||||
setSelectedTab(!selectedTab ? null : { ...selectedTab });
|
||||
setIsNewTabWindow(isNewTabWindow);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent('setTabsToNav', setTabsToNav);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent('setTabsToNav', setTabsToNav);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const isSelectedAppPinned = !!sortablePinnedApps?.find(
|
||||
(item) =>
|
||||
item?.name === selectedTab?.name && item?.service === selectedTab?.service
|
||||
);
|
||||
return (
|
||||
<AppsNavBarParent>
|
||||
<AppsNavBarLeft>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
executeEvent('navigateBack', selectedTab?.tabId);
|
||||
}}
|
||||
disabled={isDisableBackButton}
|
||||
sx={{
|
||||
opacity: !isDisableBackButton ? 1 : 0.3,
|
||||
cursor: !isDisableBackButton ? 'pointer' : 'default',
|
||||
}}
|
||||
>
|
||||
<NavBack />
|
||||
</ButtonBase>
|
||||
|
||||
<Tabs
|
||||
ref={tabsRef}
|
||||
aria-label="basic tabs example"
|
||||
variant="scrollable" // Make tabs scrollable
|
||||
scrollButtons={false}
|
||||
sx={{
|
||||
'& .MuiTabs-indicator': {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
maxWidth: `calc(100vw - 150px)`, // Ensure the tabs container fits within the available space
|
||||
overflow: 'hidden', // Prevents overflow on small screens
|
||||
}}
|
||||
>
|
||||
{tabs?.map((tab) => (
|
||||
<Tab
|
||||
key={tab.tabId}
|
||||
label={
|
||||
<TabComponent
|
||||
isSelected={
|
||||
tab?.tabId === selectedTab?.tabId && !isNewTabWindow
|
||||
}
|
||||
app={tab}
|
||||
/>
|
||||
} // Pass custom component
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
color: 'white',
|
||||
},
|
||||
padding: '0px',
|
||||
margin: '0px',
|
||||
minWidth: '0px',
|
||||
width: '50px',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</AppsNavBarLeft>
|
||||
|
||||
{selectedTab && (
|
||||
<AppsNavBarRight
|
||||
sx={{
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setSelectedTab(null);
|
||||
executeEvent('newTabWindow', {});
|
||||
}}
|
||||
>
|
||||
<NavAdd
|
||||
style={{
|
||||
height: '40px',
|
||||
width: '40px',
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
onClick={(e) => {
|
||||
if (!selectedTab) return;
|
||||
handleClick(e);
|
||||
}}
|
||||
>
|
||||
<NavMoreMenu
|
||||
style={{
|
||||
height: '34px',
|
||||
width: '34px',
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
</AppsNavBarRight>
|
||||
)}
|
||||
|
||||
<Menu
|
||||
id="navbar-more-mobile"
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'basic-button',
|
||||
}}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
backgroundColor: 'var(--bg-primary)',
|
||||
color: '#fff',
|
||||
width: '148px',
|
||||
borderRadius: '5px',
|
||||
},
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
marginTop: '10px',
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
if (!selectedTab) return;
|
||||
|
||||
setSortablePinnedApps((prev) => {
|
||||
let updatedApps;
|
||||
|
||||
if (isSelectedAppPinned) {
|
||||
// Remove the selected app if it is pinned
|
||||
updatedApps = prev.filter(
|
||||
(item) =>
|
||||
!(
|
||||
item?.name === selectedTab?.name &&
|
||||
item?.service === selectedTab?.service
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Add the selected app if it is not pinned
|
||||
updatedApps = [
|
||||
...prev,
|
||||
{
|
||||
name: selectedTab?.name,
|
||||
service: selectedTab?.service,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
saveToLocalStorage(
|
||||
'ext_saved_settings',
|
||||
'sortablePinnedApps',
|
||||
updatedApps
|
||||
);
|
||||
return updatedApps;
|
||||
});
|
||||
setSettingsLocalLastUpdated(Date.now());
|
||||
|
||||
handleClose();
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: '24px !important',
|
||||
marginRight: '5px',
|
||||
}}
|
||||
>
|
||||
<PushPinIcon
|
||||
height={20}
|
||||
sx={{
|
||||
color: isSelectedAppPinned ? 'red' : 'rgba(250, 250, 250, 0.5)',
|
||||
}}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
|
||||
<ListItemText
|
||||
sx={{
|
||||
'& .MuiTypography-root': {
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
color: isSelectedAppPinned ? 'red' : 'rgba(250, 250, 250, 0.5)',
|
||||
},
|
||||
}}
|
||||
primary={`${isSelectedAppPinned ? 'Unpin app' : 'Pin app'}`}
|
||||
/>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
executeEvent('refreshApp', {
|
||||
tabId: selectedTab?.tabId,
|
||||
});
|
||||
handleClose();
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: '24px !important',
|
||||
marginRight: '5px',
|
||||
}}
|
||||
>
|
||||
<RefreshIcon
|
||||
height={20}
|
||||
sx={{
|
||||
color: 'rgba(250, 250, 250, 0.5)',
|
||||
}}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
|
||||
<ListItemText
|
||||
sx={{
|
||||
'& .MuiTypography-root': {
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
color: 'rgba(250, 250, 250, 0.5)',
|
||||
},
|
||||
}}
|
||||
primary="Refresh"
|
||||
/>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</AppsNavBarParent>
|
||||
);
|
||||
};
|
@ -6,7 +6,6 @@ import {
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Input,
|
||||
MenuItem,
|
||||
Select,
|
||||
@ -24,15 +23,15 @@ import {
|
||||
import { Label } from '../Group/AddGroup';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import {
|
||||
Add,
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
AppCircleLabel,
|
||||
PublishQAppChoseFile,
|
||||
PublishQAppInfo,
|
||||
} from './Apps-styles';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import ImageUploader from '../../common/ImageUploader';
|
||||
import { isMobile, MyContext } from '../../App';
|
||||
import { MyContext } from '../../App';
|
||||
import { fileToBase64 } from '../../utils/fileReading';
|
||||
import { objectToBase64 } from '../../qdn/encryption/group-encryption';
|
||||
import { getFee } from '../../background';
|
||||
@ -67,6 +66,7 @@ export const AppsPrivate = ({ myName }) => {
|
||||
(group) => groupsProperties[group?.groupId]?.isOpen === false
|
||||
);
|
||||
}, [memberGroups, groupsProperties]);
|
||||
|
||||
const [privateAppValues, setPrivateAppValues] = useState({
|
||||
name: '',
|
||||
service: 'DOCUMENT',
|
||||
@ -230,11 +230,11 @@ export const AppsPrivate = ({ myName }) => {
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? '10px' : '5px',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
<Add>+</Add>
|
||||
<AddIcon />
|
||||
</AppCircle>
|
||||
|
||||
<AppCircleLabel>Private</AppCircleLabel>
|
||||
@ -267,9 +267,8 @@ export const AppsPrivate = ({ myName }) => {
|
||||
value={valueTabPrivateApp}
|
||||
onChange={handleChange}
|
||||
aria-label="basic tabs example"
|
||||
variant={isMobile ? 'scrollable' : 'fullWidth'} // Scrollable on mobile, full width on desktop
|
||||
variant={'fullWidth'}
|
||||
scrollButtons="auto"
|
||||
allowScrollButtonsMobile
|
||||
sx={{
|
||||
'& .MuiTabs-indicator': {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
@ -283,7 +282,7 @@ export const AppsPrivate = ({ myName }) => {
|
||||
'&.Mui-selected': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
|
||||
fontSize: '1rem',
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
@ -293,7 +292,7 @@ export const AppsPrivate = ({ myName }) => {
|
||||
'&.Mui-selected': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
|
||||
fontSize: '1rem',
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
sortablePinnedAppsAtom,
|
||||
} from '../../atoms/global';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { saveToLocalStorage } from './AppsNavBar';
|
||||
import { saveToLocalStorage } from './AppsNavBarDesktop';
|
||||
import { ContextMenuPinnedApps } from '../ContextMenuPinnedApps';
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import { useHandlePrivateApps } from './useHandlePrivateApps';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { MyContext, isMobile } from '../../App';
|
||||
import { MyContext } from '../../App';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { AdminSpaceInner } from './AdminSpaceInner';
|
||||
|
||||
@ -29,10 +29,9 @@ export const AdminSpace = ({
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
// reference to change height
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: isMobile ? `calc(${rootHeight} - 127px` : 'calc(100vh - 70px)',
|
||||
height: 'calc(100vh - 70px)',
|
||||
left: hide && '-1000px',
|
||||
opacity: hide ? 0 : 1,
|
||||
position: hide ? 'fixed' : 'relative',
|
||||
|
@ -21,7 +21,6 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import {
|
||||
getArbitraryEndpointReact,
|
||||
getBaseApiReact,
|
||||
isMobile,
|
||||
pauseAllQueues,
|
||||
resumeAllQueues,
|
||||
} from '../../App';
|
||||
@ -56,12 +55,6 @@ export const AnnouncementDiscussion = ({
|
||||
const clearEditorContent = () => {
|
||||
if (editorRef.current) {
|
||||
editorRef.current.chain().focus().clearContent().run();
|
||||
if (isMobile) {
|
||||
setTimeout(() => {
|
||||
editorRef.current?.chain().blur().run();
|
||||
setIsFocusedParent(false);
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -278,7 +271,7 @@ export const AnnouncementDiscussion = ({
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: isMobile ? '100%' : '100%',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
|
@ -20,7 +20,6 @@ import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
import {
|
||||
getBaseApiReact,
|
||||
getBaseApiReactSocket,
|
||||
isMobile,
|
||||
pauseAllQueues,
|
||||
resumeAllQueues,
|
||||
} from '../../App';
|
||||
@ -410,16 +409,6 @@ export const ChatDirect = ({
|
||||
if (editorRef.current) {
|
||||
setMessageSize(0);
|
||||
editorRef.current.chain().focus().clearContent().run();
|
||||
if (isMobile) {
|
||||
setTimeout(() => {
|
||||
editorRef.current?.chain().blur().run();
|
||||
setIsFocusedParent(false);
|
||||
executeEvent('sent-new-message-group', {});
|
||||
setTimeout(() => {
|
||||
triggerRerender();
|
||||
}, 300);
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
@ -547,108 +536,41 @@ export const ChatDirect = ({
|
||||
background: theme.palette.background.default,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: isMobile ? '100%' : '100vh',
|
||||
height: '100vh',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{!isMobile && (
|
||||
<Box
|
||||
onClick={close}
|
||||
<Box
|
||||
onClick={close}
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
alignSelf: 'center',
|
||||
background: theme.palette.background.default,
|
||||
borderRadius: '3px',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
margin: '10px 0px',
|
||||
padding: '4px 6px',
|
||||
width: 'fit-content',
|
||||
}}
|
||||
>
|
||||
<ArrowBackIcon
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
alignSelf: 'center',
|
||||
background: theme.palette.background.default,
|
||||
borderRadius: '3px',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
margin: '10px 0px',
|
||||
padding: '4px 6px',
|
||||
width: 'fit-content',
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '20px',
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
<ArrowBackIcon
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '20px',
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
Close Direct Chat
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
height: '15px',
|
||||
justifyContent: 'center',
|
||||
marginTop: '14px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '320px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
width: '50px',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
close();
|
||||
}}
|
||||
>
|
||||
<ReturnIcon />
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '14px',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{isNewChat
|
||||
? ''
|
||||
: selectedDirect?.name ||
|
||||
selectedDirect?.address?.slice(0, 10) + '...'}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
width: '50px',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setSelectedDirect(null);
|
||||
setMobileViewModeKeepOpen('');
|
||||
setNewChat(false);
|
||||
}}
|
||||
>
|
||||
<ExitIcon />
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
Close Direct Chat
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{isNewChat && (
|
||||
<>
|
||||
<Spacer height="30px" />
|
||||
@ -685,7 +607,7 @@ export const ChatDirect = ({
|
||||
flexShrink: 0,
|
||||
minHeight: '150px',
|
||||
overflow: 'hidden',
|
||||
padding: isMobile ? '10px' : '20px',
|
||||
padding: '20px',
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
width: '100%',
|
||||
@ -753,7 +675,7 @@ export const ChatDirect = ({
|
||||
setEditorRef={setEditorRef}
|
||||
onEnter={sendMessage}
|
||||
isChat
|
||||
disableEnter={isMobile ? true : false}
|
||||
disableEnter={false}
|
||||
setIsFocusedParent={setIsFocusedParent}
|
||||
/>
|
||||
{messageSize > 750 && (
|
||||
|
@ -20,7 +20,6 @@ import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
|
||||
import {
|
||||
getBaseApiReact,
|
||||
getBaseApiReactSocket,
|
||||
isMobile,
|
||||
MyContext,
|
||||
pauseAllQueues,
|
||||
resumeAllQueues,
|
||||
@ -746,16 +745,6 @@ export const ChatGroup = ({
|
||||
if (editorRef.current) {
|
||||
setMessageSize(0);
|
||||
editorRef.current.chain().focus().clearContent().run();
|
||||
if (isMobile) {
|
||||
setTimeout(() => {
|
||||
editorRef.current?.chain().blur().run();
|
||||
setIsFocusedParent(false);
|
||||
executeEvent('sent-new-message-group', {});
|
||||
setTimeout(() => {
|
||||
triggerRerender();
|
||||
}, 300);
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -1109,7 +1098,7 @@ export const ChatGroup = ({
|
||||
setEditorRef={setEditorRef}
|
||||
onEnter={sendMessage}
|
||||
isChat
|
||||
disableEnter={isMobile ? true : false}
|
||||
disableEnter={false}
|
||||
isFocusedParent={isFocusedParent}
|
||||
setIsFocusedParent={setIsFocusedParent}
|
||||
membersWithNames={members}
|
||||
|
@ -26,7 +26,6 @@ import {
|
||||
MyContext,
|
||||
getArbitraryEndpointReact,
|
||||
getBaseApiReact,
|
||||
isMobile,
|
||||
pauseAllQueues,
|
||||
resumeAllQueues,
|
||||
} from '../../App';
|
||||
@ -262,15 +261,6 @@ export const GroupAnnouncements = ({
|
||||
const clearEditorContent = () => {
|
||||
if (editorRef.current) {
|
||||
editorRef.current.chain().focus().clearContent().run();
|
||||
if (isMobile) {
|
||||
setTimeout(() => {
|
||||
editorRef.current?.chain().blur().run();
|
||||
setIsFocusedParent(false);
|
||||
setTimeout(() => {
|
||||
triggerRerender();
|
||||
}, 300);
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -529,12 +519,9 @@ export const GroupAnnouncements = ({
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
// reference to change height
|
||||
height: isMobile
|
||||
? `calc(${rootHeight} - 127px`
|
||||
: 'calc(100vh - 70px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: 'calc(100vh - 70px)',
|
||||
left: hide && '-1000px',
|
||||
position: hide && 'fixed',
|
||||
visibility: hide && 'hidden',
|
||||
@ -576,26 +563,24 @@ export const GroupAnnouncements = ({
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{!isMobile && (
|
||||
<Box
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
fontSize: '20px',
|
||||
gap: '20px',
|
||||
justifyContent: 'center',
|
||||
padding: '25px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<CampaignIcon
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
fontSize: '20px',
|
||||
gap: '20px',
|
||||
justifyContent: 'center',
|
||||
padding: '25px',
|
||||
width: '100%',
|
||||
fontSize: '30px',
|
||||
}}
|
||||
>
|
||||
<CampaignIcon
|
||||
sx={{
|
||||
fontSize: '30px',
|
||||
}}
|
||||
/>
|
||||
Group Announcements
|
||||
</Box>
|
||||
)}
|
||||
/>
|
||||
Group Announcements
|
||||
</Box>
|
||||
|
||||
<Spacer height={'25px'} />
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { GroupMail } from '../Group/Forum/GroupMail';
|
||||
import { MyContext, isMobile } from '../../App';
|
||||
import { MyContext } from '../../App';
|
||||
|
||||
export const GroupForum = ({
|
||||
selectedGroup,
|
||||
|
@ -23,7 +23,6 @@ import DeveloperModeIcon from '@mui/icons-material/DeveloperMode';
|
||||
import Compressor from 'compressorjs';
|
||||
import Mention from '@tiptap/extension-mention';
|
||||
import ImageResize from 'tiptap-extension-resize-image'; // Import the ResizeImage extension
|
||||
import { isMobile } from '../../App';
|
||||
import tippy from 'tippy.js';
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
import { ReactRenderer } from '@tiptap/react';
|
||||
@ -137,7 +136,7 @@ const MenuBar = ({
|
||||
color: editor.isActive('bold')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
padding: 'revert',
|
||||
}}
|
||||
>
|
||||
<FormatBoldIcon />
|
||||
@ -149,7 +148,7 @@ const MenuBar = ({
|
||||
color: editor.isActive('italic')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
padding: 'revert',
|
||||
}}
|
||||
>
|
||||
<FormatItalicIcon />
|
||||
@ -161,7 +160,7 @@ const MenuBar = ({
|
||||
color: editor.isActive('strike')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
padding: 'revert',
|
||||
}}
|
||||
>
|
||||
<StrikethroughSIcon />
|
||||
@ -173,7 +172,7 @@ const MenuBar = ({
|
||||
color: editor.isActive('code')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
padding: 'revert',
|
||||
}}
|
||||
>
|
||||
<CodeIcon />
|
||||
@ -188,7 +187,7 @@ const MenuBar = ({
|
||||
editor.isActive('code')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
padding: 'revert',
|
||||
}}
|
||||
>
|
||||
<FormatClearIcon />
|
||||
@ -199,7 +198,7 @@ const MenuBar = ({
|
||||
color: editor.isActive('bulletList')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
padding: 'revert',
|
||||
}}
|
||||
>
|
||||
<FormatListBulletedIcon />
|
||||
@ -210,7 +209,7 @@ const MenuBar = ({
|
||||
color: editor.isActive('orderedList')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
padding: 'revert',
|
||||
}}
|
||||
>
|
||||
<FormatListNumberedIcon />
|
||||
@ -221,7 +220,7 @@ const MenuBar = ({
|
||||
color: editor.isActive('codeBlock')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
padding: 'revert',
|
||||
}}
|
||||
>
|
||||
<DeveloperModeIcon />
|
||||
@ -232,7 +231,7 @@ const MenuBar = ({
|
||||
color: editor.isActive('blockquote')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
padding: 'revert',
|
||||
}}
|
||||
>
|
||||
<FormatQuoteIcon />
|
||||
@ -240,7 +239,7 @@ const MenuBar = ({
|
||||
<IconButton
|
||||
onClick={() => editor.chain().focus().setHorizontalRule().run()}
|
||||
disabled={!editor.can().chain().focus().setHorizontalRule().run()}
|
||||
sx={{ color: 'gray', padding: isMobile ? '5px' : 'revert' }}
|
||||
sx={{ color: 'gray', padding: 'revert' }}
|
||||
>
|
||||
<HorizontalRuleIcon />
|
||||
</IconButton>
|
||||
@ -252,7 +251,7 @@ const MenuBar = ({
|
||||
color: editor.isActive('heading', { level: 1 })
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
padding: 'revert',
|
||||
}}
|
||||
>
|
||||
<FormatHeadingIcon fontSize="small" />
|
||||
@ -260,7 +259,7 @@ const MenuBar = ({
|
||||
<IconButton
|
||||
onClick={() => editor.chain().focus().undo().run()}
|
||||
disabled={!editor.can().chain().focus().undo().run()}
|
||||
sx={{ color: 'gray', padding: isMobile ? '5px' : 'revert' }}
|
||||
sx={{ color: 'gray', padding: 'revert' }}
|
||||
>
|
||||
<UndoIcon />
|
||||
</IconButton>
|
||||
@ -313,7 +312,7 @@ const MenuBar = ({
|
||||
onClick={triggerImageUpload}
|
||||
sx={{
|
||||
color: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
padding: 'revert',
|
||||
}}
|
||||
>
|
||||
<ImageIcon />
|
||||
@ -398,11 +397,6 @@ export default ({
|
||||
usersRef.current = users; // Keep users up-to-date
|
||||
}, [users]);
|
||||
|
||||
const handleFocus = () => {
|
||||
if (!isMobile) return;
|
||||
setIsFocusedParent(true);
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
const htmlContent = editorRef.current.getHTML();
|
||||
if (!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') {
|
||||
@ -499,7 +493,7 @@ export default ({
|
||||
>
|
||||
<EditorProvider
|
||||
slotBefore={
|
||||
(isFocusedParent || !isMobile || overrideMobile) && (
|
||||
(isFocusedParent || overrideMobile) && (
|
||||
<MenuBar
|
||||
setEditorRef={setEditorRefFunc}
|
||||
isChat={isChat}
|
||||
@ -511,21 +505,15 @@ export default ({
|
||||
extensions={[...extensionsFiltered, ...additionalExtensions]}
|
||||
content={content}
|
||||
onCreate={({ editor }) => {
|
||||
editor.on('focus', handleFocus); // Listen for focus event
|
||||
editor.on('blur', handleBlur); // Listen for blur event
|
||||
}}
|
||||
onUpdate={({ editor }) => {
|
||||
editor.on('focus', handleFocus); // Ensure focus is updated
|
||||
editor.on('blur', handleBlur); // Ensure blur is updated
|
||||
}}
|
||||
editorProps={{
|
||||
attributes: {
|
||||
class: 'tiptap-prosemirror',
|
||||
style: isMobile
|
||||
? `overflow: auto; min-height: ${
|
||||
customEditorHeight ? '200px' : '0px'
|
||||
}; max-height:calc(100svh - ${customEditorHeight || '140px'})`
|
||||
: `overflow: auto; max-height: 250px`,
|
||||
style: `overflow: auto; max-height: 250px`,
|
||||
},
|
||||
handleKeyDown(view, event) {
|
||||
if (
|
||||
|
@ -1,152 +1,182 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { ListItemIcon, Menu, MenuItem, Typography, styled } from '@mui/material';
|
||||
import {
|
||||
ListItemIcon,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Typography,
|
||||
styled,
|
||||
} from '@mui/material';
|
||||
import PushPinIcon from '@mui/icons-material/PushPin';
|
||||
import { saveToLocalStorage } from './Apps/AppsNavBar';
|
||||
import { saveToLocalStorage } from './Apps/AppsNavBarDesktop';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { sortablePinnedAppsAtom } from '../atoms/global';
|
||||
|
||||
const CustomStyledMenu = styled(Menu)(({ theme }) => ({
|
||||
'& .MuiPaper-root': {
|
||||
backgroundColor: '#f9f9f9',
|
||||
borderRadius: '12px',
|
||||
padding: theme.spacing(1),
|
||||
boxShadow: '0 5px 15px rgba(0, 0, 0, 0.2)',
|
||||
},
|
||||
'& .MuiMenuItem-root': {
|
||||
fontSize: '14px',
|
||||
color: '#444',
|
||||
transition: '0.3s background-color',
|
||||
'&:hover': {
|
||||
backgroundColor: '#f0f0f0',
|
||||
},
|
||||
'& .MuiPaper-root': {
|
||||
backgroundColor: '#f9f9f9',
|
||||
borderRadius: '12px',
|
||||
padding: theme.spacing(1),
|
||||
boxShadow: '0 5px 15px rgba(0, 0, 0, 0.2)',
|
||||
},
|
||||
'& .MuiMenuItem-root': {
|
||||
fontSize: '14px',
|
||||
color: '#444',
|
||||
transition: '0.3s background-color',
|
||||
'&:hover': {
|
||||
backgroundColor: '#f0f0f0',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export const ContextMenuPinnedApps = ({ children, app, isMine }) => {
|
||||
const [menuPosition, setMenuPosition] = useState(null);
|
||||
const longPressTimeout = useRef(null);
|
||||
const maxHoldTimeout = useRef(null);
|
||||
const preventClick = useRef(false);
|
||||
const startTouchPosition = useRef({ x: 0, y: 0 }); // Track initial touch position
|
||||
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(sortablePinnedAppsAtom);
|
||||
const [menuPosition, setMenuPosition] = useState(null);
|
||||
const longPressTimeout = useRef(null);
|
||||
const maxHoldTimeout = useRef(null);
|
||||
const preventClick = useRef(false);
|
||||
const startTouchPosition = useRef({ x: 0, y: 0 }); // Track initial touch position
|
||||
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(
|
||||
sortablePinnedAppsAtom
|
||||
);
|
||||
|
||||
const handleContextMenu = (event) => {
|
||||
if(isMine) return
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
preventClick.current = true;
|
||||
setMenuPosition({
|
||||
mouseX: event.clientX,
|
||||
mouseY: event.clientY,
|
||||
});
|
||||
};
|
||||
const handleContextMenu = (event) => {
|
||||
if (isMine) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
preventClick.current = true;
|
||||
setMenuPosition({
|
||||
mouseX: event.clientX,
|
||||
mouseY: event.clientY,
|
||||
});
|
||||
};
|
||||
|
||||
const handleTouchStart = (event) => {
|
||||
if(isMine) return
|
||||
const handleTouchStart = (event) => {
|
||||
if (isMine) return;
|
||||
|
||||
const { clientX, clientY } = event.touches[0];
|
||||
startTouchPosition.current = { x: clientX, y: clientY };
|
||||
const { clientX, clientY } = event.touches[0];
|
||||
startTouchPosition.current = { x: clientX, y: clientY };
|
||||
|
||||
longPressTimeout.current = setTimeout(() => {
|
||||
preventClick.current = true;
|
||||
|
||||
event.stopPropagation();
|
||||
setMenuPosition({
|
||||
mouseX: clientX,
|
||||
mouseY: clientY,
|
||||
longPressTimeout.current = setTimeout(() => {
|
||||
preventClick.current = true;
|
||||
|
||||
event.stopPropagation();
|
||||
setMenuPosition({
|
||||
mouseX: clientX,
|
||||
mouseY: clientY,
|
||||
});
|
||||
}, 500);
|
||||
|
||||
// Set a maximum hold duration (e.g., 1.5 seconds)
|
||||
maxHoldTimeout.current = setTimeout(() => {
|
||||
clearTimeout(longPressTimeout.current);
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const handleTouchMove = (event) => {
|
||||
if (isMine) return;
|
||||
|
||||
const { clientX, clientY } = event.touches[0];
|
||||
const { x, y } = startTouchPosition.current;
|
||||
|
||||
// Determine if the touch has moved beyond a small threshold (e.g., 10px)
|
||||
const movedEnough =
|
||||
Math.abs(clientX - x) > 10 || Math.abs(clientY - y) > 10;
|
||||
|
||||
if (movedEnough) {
|
||||
clearTimeout(longPressTimeout.current);
|
||||
clearTimeout(maxHoldTimeout.current);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTouchEnd = (event) => {
|
||||
if (isMine) return;
|
||||
|
||||
clearTimeout(longPressTimeout.current);
|
||||
clearTimeout(maxHoldTimeout.current);
|
||||
if (preventClick.current) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
preventClick.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = (e) => {
|
||||
if (isMine) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setMenuPosition(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onContextMenu={handleContextMenu}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchMove={handleTouchMove}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
style={{ touchAction: 'none' }}
|
||||
>
|
||||
{children}
|
||||
<CustomStyledMenu
|
||||
disableAutoFocusItem
|
||||
open={!!menuPosition}
|
||||
onClose={handleClose}
|
||||
anchorReference="anchorPosition"
|
||||
anchorPosition={
|
||||
menuPosition
|
||||
? { top: menuPosition.mouseY, left: menuPosition.mouseX }
|
||||
: undefined
|
||||
}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
onClick={(e) => {
|
||||
handleClose(e);
|
||||
setSortablePinnedApps((prev) => {
|
||||
if (app?.isPrivate) {
|
||||
const updatedApps = prev.filter(
|
||||
(item) =>
|
||||
!(
|
||||
item?.privateAppProperties?.name ===
|
||||
app?.privateAppProperties?.name &&
|
||||
item?.privateAppProperties?.service ===
|
||||
app?.privateAppProperties?.service &&
|
||||
item?.privateAppProperties?.identifier ===
|
||||
app?.privateAppProperties?.identifier
|
||||
)
|
||||
);
|
||||
saveToLocalStorage(
|
||||
'ext_saved_settings',
|
||||
'sortablePinnedApps',
|
||||
updatedApps
|
||||
);
|
||||
return updatedApps;
|
||||
} else {
|
||||
const updatedApps = prev.filter(
|
||||
(item) =>
|
||||
!(
|
||||
item?.name === app?.name && item?.service === app?.service
|
||||
)
|
||||
);
|
||||
saveToLocalStorage(
|
||||
'ext_saved_settings',
|
||||
'sortablePinnedApps',
|
||||
updatedApps
|
||||
);
|
||||
return updatedApps;
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
|
||||
// Set a maximum hold duration (e.g., 1.5 seconds)
|
||||
maxHoldTimeout.current = setTimeout(() => {
|
||||
clearTimeout(longPressTimeout.current);
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const handleTouchMove = (event) => {
|
||||
if(isMine) return
|
||||
|
||||
const { clientX, clientY } = event.touches[0];
|
||||
const { x, y } = startTouchPosition.current;
|
||||
|
||||
// Determine if the touch has moved beyond a small threshold (e.g., 10px)
|
||||
const movedEnough = Math.abs(clientX - x) > 10 || Math.abs(clientY - y) > 10;
|
||||
|
||||
if (movedEnough) {
|
||||
clearTimeout(longPressTimeout.current);
|
||||
clearTimeout(maxHoldTimeout.current);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTouchEnd = (event) => {
|
||||
if(isMine) return
|
||||
|
||||
clearTimeout(longPressTimeout.current);
|
||||
clearTimeout(maxHoldTimeout.current);
|
||||
if (preventClick.current) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
preventClick.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = (e) => {
|
||||
if(isMine) return
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setMenuPosition(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onContextMenu={handleContextMenu}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchMove={handleTouchMove}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
style={{ touchAction: 'none' }}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<CustomStyledMenu
|
||||
disableAutoFocusItem
|
||||
open={!!menuPosition}
|
||||
onClose={handleClose}
|
||||
anchorReference="anchorPosition"
|
||||
anchorPosition={
|
||||
menuPosition
|
||||
? { top: menuPosition.mouseY, left: menuPosition.mouseX }
|
||||
: undefined
|
||||
}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<MenuItem onClick={(e) => {
|
||||
handleClose(e);
|
||||
setSortablePinnedApps((prev) => {
|
||||
if(app?.isPrivate){
|
||||
const updatedApps = prev.filter(
|
||||
(item) => !(item?.privateAppProperties?.name === app?.privateAppProperties?.name && item?.privateAppProperties?.service === app?.privateAppProperties?.service && item?.privateAppProperties?.identifier === app?.privateAppProperties?.identifier)
|
||||
);
|
||||
saveToLocalStorage('ext_saved_settings', 'sortablePinnedApps', updatedApps);
|
||||
return updatedApps;
|
||||
} else {
|
||||
const updatedApps = prev.filter(
|
||||
(item) => !(item?.name === app?.name && item?.service === app?.service)
|
||||
);
|
||||
saveToLocalStorage('ext_saved_settings', 'sortablePinnedApps', updatedApps);
|
||||
return updatedApps;
|
||||
}
|
||||
});
|
||||
}}>
|
||||
<ListItemIcon sx={{ minWidth: '32px' }}>
|
||||
<PushPinIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<Typography variant="inherit" sx={{ fontSize: '14px' }}>
|
||||
Unpin app
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
</CustomStyledMenu>
|
||||
</div>
|
||||
);
|
||||
<ListItemIcon sx={{ minWidth: '32px' }}>
|
||||
<PushPinIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<Typography variant="inherit" sx={{ fontSize: '14px' }}>
|
||||
Unpin app
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
</CustomStyledMenu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import Box from '@mui/material/Box';
|
||||
import Drawer from '@mui/material/Drawer';
|
||||
import { isMobile } from '../../App';
|
||||
export const DrawerComponent = ({ open, setOpen, children }) => {
|
||||
const toggleDrawer = (newOpen: boolean) => () => {
|
||||
setOpen(newOpen);
|
||||
@ -9,10 +8,7 @@ export const DrawerComponent = ({ open, setOpen, children }) => {
|
||||
return (
|
||||
<div>
|
||||
<Drawer open={open} onClose={toggleDrawer(false)}>
|
||||
<Box
|
||||
sx={{ width: isMobile ? '100vw' : '400px', height: '100%' }}
|
||||
role="presentation"
|
||||
>
|
||||
<Box sx={{ width: '400px', height: '100%' }} role="presentation">
|
||||
{children}
|
||||
</Box>
|
||||
</Drawer>
|
||||
|