mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-22 02:12:38 +01:00
work on mobile
- replaced banner context with banner controller like modals - replaced network check with new hook - add swipable layout component - fix member list width being slightly larger when overflowing members are displayed god help me
This commit is contained in:
parent
aaa5e78cc5
commit
960f9bf875
@ -34,6 +34,7 @@
|
|||||||
"@testing-library/user-event": "^14.5.2",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@types/react-measure": "^2.0.12",
|
"@types/react-measure": "^2.0.12",
|
||||||
"@types/react-portal": "^4.0.7",
|
"@types/react-portal": "^4.0.7",
|
||||||
|
"@uidotdev/usehooks": "^2.4.1",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
@ -60,9 +61,11 @@
|
|||||||
"react-secure-storage": "^1.3.2",
|
"react-secure-storage": "^1.3.2",
|
||||||
"react-select-search": "^4.1.7",
|
"react-select-search": "^4.1.7",
|
||||||
"react-spinners": "^0.13.8",
|
"react-spinners": "^0.13.8",
|
||||||
|
"react-spring": "^9.7.3",
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
"react-use-error-boundary": "^3.0.0",
|
"react-use-error-boundary": "^3.0.0",
|
||||||
|
"react-use-gesture": "^9.1.3",
|
||||||
"react-virtualized": "^9.22.5",
|
"react-virtualized": "^9.22.5",
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
"reoverlay": "^1.0.3",
|
"reoverlay": "^1.0.3",
|
||||||
|
@ -1,97 +1,42 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="AutoImportSettings">
|
<component name="AutoImportSettings">
|
||||||
<option name="autoReloadType" value="ALL" />
|
<option name="autoReloadType" value="NONE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="CargoProjects">
|
<component name="CargoProjects">
|
||||||
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
|
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="0756a13d-f814-41e0-81ae-eb872c2c747f" name="Changes" comment="">
|
<list default="true" id="0756a13d-f814-41e0-81ae-eb872c2c747f" name="Changes" comment="">
|
||||||
<change beforePath="$PROJECT_DIR$/../.editorconfig" beforeDir="false" afterPath="$PROJECT_DIR$/../.editorconfig" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/../package.json" beforeDir="false" afterPath="$PROJECT_DIR$/../package.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../.eslintignore" beforeDir="false" afterPath="$PROJECT_DIR$/../.eslintignore" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/../pnpm-lock.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/../pnpm-lock.yaml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../.eslintrc" beforeDir="false" afterPath="$PROJECT_DIR$/../.eslintrc" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/gen/android/.idea/.gitignore" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../.github/ISSUE_TEMPLATE/bug_report.md" beforeDir="false" afterPath="$PROJECT_DIR$/../.github/ISSUE_TEMPLATE/bug_report.md" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/gen/android/.idea/compiler.xml" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../.github/ISSUE_TEMPLATE/config.yml" beforeDir="false" afterPath="$PROJECT_DIR$/../.github/ISSUE_TEMPLATE/config.yml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/gen/android/.idea/deploymentTargetDropDown.xml" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../.github/ISSUE_TEMPLATE/feature_request.md" beforeDir="false" afterPath="$PROJECT_DIR$/../.github/ISSUE_TEMPLATE/feature_request.md" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/gen/android/.idea/discord.xml" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../.github/workflows/pages-deploy.yml" beforeDir="false" afterPath="$PROJECT_DIR$/../.github/workflows/pages-deploy.yml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/gen/android/.idea/gradle.xml" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../.github/workflows/tauri.yml" beforeDir="false" afterPath="$PROJECT_DIR$/../.github/workflows/tauri.yml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/gen/android/.idea/kotlinc.xml" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/../.gitignore" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/gen/android/.idea/migrations.xml" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../.prettierignore" beforeDir="false" afterPath="$PROJECT_DIR$/../.prettierignore" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/gen/android/.idea/misc.xml" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../.vscode/extensions.json" beforeDir="false" afterPath="$PROJECT_DIR$/../.vscode/extensions.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/gen/android/.idea/vcs.xml" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../.vscode/settings.json" beforeDir="false" afterPath="$PROJECT_DIR$/../.vscode/settings.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/gen/android/buildSrc/build.gradle.kts" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../LICENSE" beforeDir="false" afterPath="$PROJECT_DIR$/../LICENSE" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/gen/android/buildSrc/src/main/java/chat/spacebar/app/kotlin/BuildTask.kt" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../flake.nix" beforeDir="false" afterPath="$PROJECT_DIR$/../flake.nix" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/gen/android/buildSrc/src/main/java/chat/spacebar/app/kotlin/RustPlugin.kt" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../flake.template.nix" beforeDir="false" afterPath="$PROJECT_DIR$/../flake.template.nix" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/../src/App.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../src/App.tsx" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../nix-build-test.sh" beforeDir="false" afterPath="$PROJECT_DIR$/../nix-build-test.sh" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/../src/components/ChannelSidebar.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../src/components/ChannelSidebar.tsx" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../nix-rebuild-flake.sh" beforeDir="false" afterPath="$PROJECT_DIR$/../nix-rebuild-flake.sh" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/../src/components/GuildSidebar.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../src/components/GuildSidebar.tsx" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../public/manifest.json" beforeDir="false" afterPath="$PROJECT_DIR$/../public/manifest.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/../src/components/messaging/Chat.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../src/components/messaging/Chat.tsx" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../public/robots.txt" beforeDir="false" afterPath="$PROJECT_DIR$/../public/robots.txt" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/../src/components/modals/JoinServerModal.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../src/components/modals/JoinServerModal.tsx" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../public/splashscreen.css" beforeDir="false" afterPath="$PROJECT_DIR$/../public/splashscreen.css" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../public/splashscreen.html" beforeDir="false" afterPath="$PROJECT_DIR$/../public/splashscreen.html" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/build.rs" beforeDir="false" afterPath="$PROJECT_DIR$/build.rs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/.editorconfig" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/.editorconfig" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/.gitignore" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/.idea/compiler.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/.idea/compiler.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/.idea/deploymentTargetDropDown.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/.idea/deploymentTargetDropDown.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/.idea/discord.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/.idea/discord.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/.idea/gradle.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/.idea/gradle.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/.idea/kotlinc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/.idea/kotlinc.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/.idea/migrations.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/.idea/migrations.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/.idea/vcs.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/app/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/app/.gitignore" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/app/build.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/app/build.gradle.kts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/app/proguard-rules.pro" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/app/proguard-rules.pro" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/app/src/main/AndroidManifest.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/app/src/main/AndroidManifest.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/app/src/main/java/chat/spacebar/app/MainActivity.kt" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/app/src/main/java/chat/spacebar/app/MainActivity.kt" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/app/src/main/res/drawable/ic_launcher_background.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/app/src/main/res/drawable/ic_launcher_background.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/app/src/main/res/layout/activity_main.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/app/src/main/res/layout/activity_main.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/app/src/main/res/values-night/themes.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/app/src/main/res/values-night/themes.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/app/src/main/res/values/colors.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/app/src/main/res/values/colors.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/app/src/main/res/values/strings.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/app/src/main/res/values/strings.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/app/src/main/res/values/themes.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/app/src/main/res/values/themes.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/app/src/main/res/xml/file_paths.xml" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/app/src/main/res/xml/file_paths.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/build.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/build.gradle.kts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/buildSrc/build.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/buildSrc/build.gradle.kts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/buildSrc/src/main/java/chat/spacebar/app/kotlin/BuildTask.kt" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/buildSrc/src/main/java/chat/spacebar/app/kotlin/BuildTask.kt" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/buildSrc/src/main/java/chat/spacebar/app/kotlin/RustPlugin.kt" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/buildSrc/src/main/java/chat/spacebar/app/kotlin/RustPlugin.kt" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/gradle.properties" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/gradle.properties" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/gradle/wrapper/gradle-wrapper.properties" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/gradle/wrapper/gradle-wrapper.properties" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/gradlew" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/gradlew" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/gradlew.bat" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/gradlew.bat" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/android/settings.gradle" beforeDir="false" afterPath="$PROJECT_DIR$/gen/android/settings.gradle" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/apple/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/gen/apple/.gitignore" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/apple/ExportOptions.plist" beforeDir="false" afterPath="$PROJECT_DIR$/gen/apple/ExportOptions.plist" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/apple/Podfile" beforeDir="false" afterPath="$PROJECT_DIR$/gen/apple/Podfile" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/apple/Sources/app/bindings/bindings.h" beforeDir="false" afterPath="$PROJECT_DIR$/gen/apple/Sources/app/bindings/bindings.h" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/apple/Sources/app/main.mm" beforeDir="false" afterPath="$PROJECT_DIR$/gen/apple/Sources/app/main.mm" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/apple/app.xcodeproj/project.pbxproj" beforeDir="false" afterPath="$PROJECT_DIR$/gen/apple/app.xcodeproj/project.pbxproj" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/apple/app.xcodeproj/project.xcworkspace/contents.xcworkspacedata" beforeDir="false" afterPath="$PROJECT_DIR$/gen/apple/app.xcodeproj/project.xcworkspace/contents.xcworkspacedata" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/apple/app.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings" beforeDir="false" afterPath="$PROJECT_DIR$/gen/apple/app.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/apple/app.xcodeproj/xcshareddata/xcschemes/app_iOS.xcscheme" beforeDir="false" afterPath="$PROJECT_DIR$/gen/apple/app.xcodeproj/xcshareddata/xcschemes/app_iOS.xcscheme" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/apple/app_iOS/Info.plist" beforeDir="false" afterPath="$PROJECT_DIR$/gen/apple/app_iOS/Info.plist" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/gen/apple/app_iOS/app_iOS.entitlements" beforeDir="false" afterPath="$PROJECT_DIR$/gen/apple/app_iOS/app_iOS.entitlements" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/lib.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/lib.rs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/main.rs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/tray.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/tray.rs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../src/assets/images/logo/Logo-Blue.svg" beforeDir="false" afterPath="$PROJECT_DIR$/../src/assets/images/logo/Logo-Blue.svg" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../src/assets/images/logo/Logo-White.svg" beforeDir="false" afterPath="$PROJECT_DIR$/../src/assets/images/logo/Logo-White.svg" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../src/assets/images/logo/Spacebar_Icon.svg" beforeDir="false" afterPath="$PROJECT_DIR$/../src/assets/images/logo/Spacebar_Icon.svg" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../src/assets/images/logo/Spacebar_Logo_Blue.svg" beforeDir="false" afterPath="$PROJECT_DIR$/../src/assets/images/logo/Spacebar_Logo_Blue.svg" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../src/assets/images/logo/icon-rounded.svg" beforeDir="false" afterPath="$PROJECT_DIR$/../src/assets/images/logo/icon-rounded.svg" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../src/components/ChannelList/ChannelListItem.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../src/components/ChannelList/ChannelListItem.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../src/components/modals/ModalComponents.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../src/components/modals/ModalComponents.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../src/components/modals/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../src/components/modals/index.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../src/controllers/modals/ModalController.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../src/controllers/modals/ModalController.tsx" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/../src/controllers/modals/ModalController.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../src/controllers/modals/ModalController.tsx" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../src/controllers/modals/types.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../src/controllers/modals/types.ts" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
</component>
|
</component>
|
||||||
|
<component name="ClangdSettings">
|
||||||
|
<option name="formatViaClangd" value="false" />
|
||||||
|
</component>
|
||||||
<component name="Git.Settings">
|
<component name="Git.Settings">
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
|
||||||
</component>
|
</component>
|
||||||
@ -110,10 +55,13 @@
|
|||||||
"keyToString": {
|
"keyToString": {
|
||||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||||
"RunOnceActivity.rust.reset.selective.auto.import": "true",
|
"RunOnceActivity.rust.reset.selective.auto.import": "true",
|
||||||
|
"cf.first.check.clang-format": "false",
|
||||||
|
"cidr.known.project.marker": "true",
|
||||||
"git-widget-placeholder": "dev",
|
"git-widget-placeholder": "dev",
|
||||||
"ignore.virus.scanning.warn.message": "true",
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
"last_opened_file_path": "C:/Users/23562/Documents/Code/workspaces/spacebar/client-react/src-tauri/Cargo.toml",
|
"last_opened_file_path": "E:/client-react/src-tauri",
|
||||||
"node.js.detected.package.eslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.tslint": "true",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
|
27
src/App.tsx
27
src/App.tsx
@ -8,11 +8,11 @@ import RegistrationPage from "./pages/RegistrationPage";
|
|||||||
|
|
||||||
import { getTauriVersion, getVersion } from "@tauri-apps/api/app";
|
import { getTauriVersion, getVersion } from "@tauri-apps/api/app";
|
||||||
import { arch, locale, platform, version } from "@tauri-apps/plugin-os";
|
import { arch, locale, platform, version } from "@tauri-apps/plugin-os";
|
||||||
|
import { useNetworkState } from "@uidotdev/usehooks";
|
||||||
import { reaction } from "mobx";
|
import { reaction } from "mobx";
|
||||||
import ErrorBoundary from "./components/ErrorBoundary";
|
import ErrorBoundary from "./components/ErrorBoundary";
|
||||||
import Loader from "./components/Loader";
|
import Loader from "./components/Loader";
|
||||||
import { UnauthenticatedGuard } from "./components/guards/UnauthenticatedGuard";
|
import { UnauthenticatedGuard } from "./components/guards/UnauthenticatedGuard";
|
||||||
import { BannerContext } from "./contexts/BannerContext";
|
|
||||||
import useLogger from "./hooks/useLogger";
|
import useLogger from "./hooks/useLogger";
|
||||||
import AppPage from "./pages/AppPage";
|
import AppPage from "./pages/AppPage";
|
||||||
import LogoutPage from "./pages/LogoutPage";
|
import LogoutPage from "./pages/LogoutPage";
|
||||||
@ -21,13 +21,14 @@ import { useAppStore } from "./stores/AppStore";
|
|||||||
import { Globals } from "./utils/Globals";
|
import { Globals } from "./utils/Globals";
|
||||||
// @ts-expect-error no types
|
// @ts-expect-error no types
|
||||||
import FPSStats from "react-fps-stats";
|
import FPSStats from "react-fps-stats";
|
||||||
|
import { bannerController } from "./controllers/banners";
|
||||||
import { isTauri } from "./utils/Utils";
|
import { isTauri } from "./utils/Utils";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
const bannerContext = React.useContext(BannerContext);
|
|
||||||
const logger = useLogger("App");
|
const logger = useLogger("App");
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const networkState = useNetworkState();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// Handles gateway connection/disconnection on token change
|
// Handles gateway connection/disconnection on token change
|
||||||
@ -78,15 +79,19 @@ function App() {
|
|||||||
return dispose;
|
return dispose;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// TODO: we need more of a stack for banners, closing here will cause any banners to immediately close
|
React.useEffect(() => {
|
||||||
// React.useEffect(() => {
|
if (!networkState.online) {
|
||||||
// if (!app.isNetworkConnected)
|
bannerController.push(
|
||||||
// bannerContext.setContent({
|
{
|
||||||
// forced: true,
|
type: "offline",
|
||||||
// element: <OfflineBanner />,
|
},
|
||||||
// });
|
"offline",
|
||||||
// else bannerContext.close();
|
);
|
||||||
// }, [app.isNetworkConnected, bannerContext]);
|
} else {
|
||||||
|
// only close if the current banner is the offline banner
|
||||||
|
bannerController.remove("offline");
|
||||||
|
}
|
||||||
|
}, [networkState]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary section="app">
|
<ErrorBoundary section="app">
|
||||||
|
@ -6,8 +6,8 @@ export const Wrapper = styled(Container)`
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100vh;
|
|
||||||
background-color: var(--background-tertiary);
|
background-color: var(--background-tertiary);
|
||||||
|
flex: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const AuthContainer = styled(Container)`
|
export const AuthContainer = styled(Container)`
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
import { AnimatePresence, motion } from "framer-motion";
|
|
||||||
import React from "react";
|
|
||||||
import styled from "styled-components";
|
|
||||||
import { BannerContext } from "../contexts/BannerContext";
|
|
||||||
import useLogger from "../hooks/useLogger";
|
|
||||||
import Icon from "./Icon";
|
|
||||||
import IconButton from "./IconButton";
|
|
||||||
|
|
||||||
const Container = styled(motion.div)`
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CloseWrapper = styled(IconButton)`
|
|
||||||
position: absolute;
|
|
||||||
right: 1%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
function Banner() {
|
|
||||||
const logger = useLogger("Banner");
|
|
||||||
const bannerContext = React.useContext(BannerContext);
|
|
||||||
|
|
||||||
if (!bannerContext.content) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AnimatePresence>
|
|
||||||
<Container
|
|
||||||
variants={{
|
|
||||||
show: {
|
|
||||||
// slide down
|
|
||||||
y: 0,
|
|
||||||
transition: {
|
|
||||||
delayChildren: 0.3,
|
|
||||||
staggerChildren: 0.2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hide: {
|
|
||||||
// slide up
|
|
||||||
y: "-100%",
|
|
||||||
transition: {
|
|
||||||
delayChildren: 0.3,
|
|
||||||
staggerChildren: 0.2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
initial="hide"
|
|
||||||
animate="show"
|
|
||||||
exit="hide"
|
|
||||||
onAnimationComplete={() => {
|
|
||||||
logger.debug("animation complete");
|
|
||||||
}}
|
|
||||||
style={bannerContext.content.style}
|
|
||||||
>
|
|
||||||
{bannerContext.content.element}
|
|
||||||
{!bannerContext.content.forced && (
|
|
||||||
<CloseWrapper
|
|
||||||
onClick={() => {
|
|
||||||
bannerContext.close();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon icon="mdiClose" color="var(--text)" size="24px" />
|
|
||||||
</CloseWrapper>
|
|
||||||
)}
|
|
||||||
</Container>
|
|
||||||
</AnimatePresence>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Banner;
|
|
@ -1,5 +1,9 @@
|
|||||||
|
import { useMediaQuery, useWindowSize } from "@uidotdev/usehooks";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { isDesktop } from "react-device-detect";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
import { isTouchscreenDevice } from "../utils/isTouchscreenDevice";
|
||||||
import ChannelHeader from "./ChannelHeader";
|
import ChannelHeader from "./ChannelHeader";
|
||||||
import ChannelList from "./ChannelList/ChannelList";
|
import ChannelList from "./ChannelList/ChannelList";
|
||||||
import Container from "./Container";
|
import Container from "./Container";
|
||||||
@ -8,21 +12,36 @@ import UserPanel from "./UserPanel";
|
|||||||
const Wrapper = styled(Container)`
|
const Wrapper = styled(Container)`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 0 0 240px;
|
|
||||||
background-color: var(--background-secondary);
|
background-color: var(--background-secondary);
|
||||||
|
|
||||||
@media (max-width: 810px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function ChannelSidebar() {
|
function ChannelSidebar() {
|
||||||
|
const windowSize = useWindowSize();
|
||||||
|
const isSmallScreen = useMediaQuery("only screen and (max-width: 810px)");
|
||||||
|
const [size, setSize] = useState<number | undefined>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!windowSize.width) return;
|
||||||
|
const screenPercent = (windowSize.width * 80) / 100;
|
||||||
|
setSize(screenPercent - 72);
|
||||||
|
}, [windowSize]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper
|
||||||
|
style={
|
||||||
|
isSmallScreen && !isDesktop
|
||||||
|
? {
|
||||||
|
width: size,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
flex: "0 0 240px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
{/* TODO: replace with dm search if no guild */}
|
{/* TODO: replace with dm search if no guild */}
|
||||||
<ChannelHeader />
|
<ChannelHeader />
|
||||||
<ChannelList />
|
<ChannelList />
|
||||||
<UserPanel />
|
{!isTouchscreenDevice && <UserPanel />}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,4 +4,5 @@ export default styled.div`
|
|||||||
background-color: var(--background-tertiary);
|
background-color: var(--background-tertiary);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
`;
|
`;
|
||||||
|
@ -13,9 +13,9 @@ const Container = styled.div`
|
|||||||
flex: 0 0 72px;
|
flex: 0 0 72px;
|
||||||
margin: 4px 0 0 0;
|
margin: 4px 0 0 0;
|
||||||
|
|
||||||
@media (max-width: 560px) {
|
// @media (max-width: 560px) {
|
||||||
display: none;
|
// display: none;
|
||||||
}
|
// }
|
||||||
|
|
||||||
.ReactVirtualized__List {
|
.ReactVirtualized__List {
|
||||||
scrollbar-width: none; /* Firefox */
|
scrollbar-width: none; /* Firefox */
|
||||||
|
@ -12,7 +12,7 @@ const Container = styled.div`
|
|||||||
flex: 0 0 240px;
|
flex: 0 0 240px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: var(--background-secondary);
|
background-color: var(--background-secondary);
|
||||||
height: 100%;
|
overflow-x: hidden;
|
||||||
|
|
||||||
@media (max-width: 1050px) {
|
@media (max-width: 1050px) {
|
||||||
display: none;
|
display: none;
|
||||||
@ -23,9 +23,9 @@ const List = styled.ul`
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
overflow-y: auto;
|
// overflow-y: auto;
|
||||||
height: 100%;
|
// height: 100%;
|
||||||
width: 100%;
|
// width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function MemberList() {
|
function MemberList() {
|
||||||
@ -59,7 +59,7 @@ function MemberList() {
|
|||||||
<MemberListItem item={x} />
|
<MemberListItem item={x} />
|
||||||
))}
|
))}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
: null}
|
: null}
|
||||||
</List>
|
</List>
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -8,5 +8,5 @@ export const SectionHeader = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
height: 24px;
|
height: 50px;
|
||||||
`;
|
`;
|
||||||
|
80
src/components/SwipeableLayout.tsx
Normal file
80
src/components/SwipeableLayout.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { useWindowSize } from "@uidotdev/usehooks";
|
||||||
|
import React from "react";
|
||||||
|
import { animated, config, useSpring } from "react-spring";
|
||||||
|
import { useDrag } from "react-use-gesture";
|
||||||
|
|
||||||
|
import styles from "./styles.module.css";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
leftChildren: React.ReactNode;
|
||||||
|
rightChildren: React.ReactNode;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SwipeableLayout({ leftChildren, children, rightChildren }: Props) {
|
||||||
|
const size = useWindowSize();
|
||||||
|
const [{ x }, api] = useSpring(() => ({
|
||||||
|
x: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const open = (canceled: boolean) => {
|
||||||
|
// when cancel is true, it means that the user passed the upwards threshold
|
||||||
|
// so we change the spring config to create a nice wobbly effect
|
||||||
|
api.start({ x: (size.width! * 80) / 100, immediate: false, config: canceled ? config.wobbly : config.stiff });
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = (velocity = 0) => {
|
||||||
|
api.start({ x: 0, immediate: false, config: { ...config.stiff, velocity } });
|
||||||
|
};
|
||||||
|
|
||||||
|
const bind = useDrag(
|
||||||
|
({ last, velocity: v, direction: [dx], offset: [ox], cancel, canceled }) => {
|
||||||
|
const maxWidth = size.width! * 0.5;
|
||||||
|
console.debug("=-=-=-=-=-=-=-=-=-=-");
|
||||||
|
console.debug(`X is: `, x.get());
|
||||||
|
console.debug(`Max width is: `, maxWidth);
|
||||||
|
console.debug(`Last`, last);
|
||||||
|
console.debug(`Velocity is: `, v);
|
||||||
|
console.debug(`Direction is: `, dx);
|
||||||
|
console.debug(`Offset is: `, ox);
|
||||||
|
|
||||||
|
// // on release, check if passed threshold to close, or reset to open pos
|
||||||
|
// if (last) {
|
||||||
|
// // if direction is < 0 (left), and offset is less than 50% of the screen width then close
|
||||||
|
|
||||||
|
// ox < maxWidth && dx === -1 ? close(v) : open(canceled);
|
||||||
|
// } else api.start({ x: ox });
|
||||||
|
|
||||||
|
api.start({ x: ox });
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: () => [x.get(), 0],
|
||||||
|
filterTaps: true,
|
||||||
|
bounds: { left: 0, right: (size.width! * 80) / 100 },
|
||||||
|
rubberband: true,
|
||||||
|
// initial: () => [x.get(), 0],
|
||||||
|
axis: "x",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// handle resize
|
||||||
|
React.useEffect(() => {
|
||||||
|
console.log("width change");
|
||||||
|
if (x.get() > 0) {
|
||||||
|
open(false);
|
||||||
|
} else {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}, [size.width]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<animated.div {...bind()} className={styles.item}>
|
||||||
|
{leftChildren}
|
||||||
|
<animated.div className={styles.fg} style={{ x }}>
|
||||||
|
{children}
|
||||||
|
</animated.div>
|
||||||
|
</animated.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SwipeableLayout;
|
37
src/components/styles.module.css
Normal file
37
src/components/styles.module.css
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
.item {
|
||||||
|
position: relative;
|
||||||
|
/* width: 100%; */
|
||||||
|
/* height: 100%; */
|
||||||
|
pointer-events: auto;
|
||||||
|
/* transform-origin: 50% 50% 0px; */
|
||||||
|
/* padding-left: 32px; */
|
||||||
|
/* padding-right: 32px; */
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
/* align-items: center; */
|
||||||
|
/* text-align: center; */
|
||||||
|
/* border-radius: 5px; */
|
||||||
|
/* box-shadow: 0px 10px 10px -5px rgba(0, 0, 0, 0.2); */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fg {
|
||||||
|
cursor: -webkit-grab;
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fg > * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
// context to handle banner open/close state
|
|
||||||
|
|
||||||
import { MotionStyle } from "framer-motion";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export interface BannerContent {
|
|
||||||
element: React.ReactNode;
|
|
||||||
style?: MotionStyle;
|
|
||||||
forced?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BannerContextType = {
|
|
||||||
content?: BannerContent;
|
|
||||||
setContent: React.Dispatch<React.SetStateAction<BannerContent | undefined>>;
|
|
||||||
close: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
// @ts-expect-error not specifying a default value here
|
|
||||||
export const BannerContext = React.createContext<BannerContextType>();
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export const BannerContextProvider: React.FC<any> = ({ children }) => {
|
|
||||||
const [content, setContent] = React.useState<BannerContent>();
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
// clear content
|
|
||||||
setContent(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
return <BannerContext.Provider value={{ content, setContent, close }}>{children}</BannerContext.Provider>;
|
|
||||||
};
|
|
168
src/controllers/banners/BannerController.tsx
Normal file
168
src/controllers/banners/BannerController.tsx
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
import { action, computed, makeObservable, observable } from "mobx";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import IconButton from "../../components/IconButton";
|
||||||
|
import OfflineBanner from "../../components/banners/OfflineBanner";
|
||||||
|
import { Banner } from "./types";
|
||||||
|
|
||||||
|
const Container = styled(motion.div)`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CloseWrapper = styled(IconButton)`
|
||||||
|
position: absolute;
|
||||||
|
right: 1%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
function randomUUID() {
|
||||||
|
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
const r = (Math.random() * 16) | 0;
|
||||||
|
// eslint-disable-next-line no-bitwise, no-mixed-operators
|
||||||
|
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
type Components = Record<string, React.FC<any>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles layering and displaying banners to the user.
|
||||||
|
*/
|
||||||
|
class BannerController<T extends Banner> {
|
||||||
|
stack: T[] = [];
|
||||||
|
components: Components;
|
||||||
|
|
||||||
|
constructor(components: Components) {
|
||||||
|
this.components = components;
|
||||||
|
|
||||||
|
makeObservable(this, {
|
||||||
|
stack: observable,
|
||||||
|
push: action,
|
||||||
|
pop: action,
|
||||||
|
remove: action,
|
||||||
|
rendered: computed,
|
||||||
|
isVisible: computed,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.close = this.close.bind(this);
|
||||||
|
this.closeAll = this.closeAll.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a new banner on the stack
|
||||||
|
* @param banner banner data
|
||||||
|
*/
|
||||||
|
push(banner: T, key?: string) {
|
||||||
|
if (key && this.stack.find((x) => x.key === key)) {
|
||||||
|
console.warn(`Banner with key '${key}' already exists on the stack!`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stack = [
|
||||||
|
...this.stack,
|
||||||
|
{
|
||||||
|
...banner,
|
||||||
|
key: key ?? randomUUID(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the top banner from the screen
|
||||||
|
*/
|
||||||
|
pop() {
|
||||||
|
this.stack = this.stack.map((entry, index) => (index === this.stack.length - 1 ? entry : entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the top banner
|
||||||
|
*/
|
||||||
|
close() {
|
||||||
|
this.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close all banners on the stack
|
||||||
|
*/
|
||||||
|
closeAll() {
|
||||||
|
this.stack = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the keyed banner from the stack
|
||||||
|
*/
|
||||||
|
remove(key: string) {
|
||||||
|
this.stack = this.stack.filter((x) => x.key !== key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render banners
|
||||||
|
*/
|
||||||
|
get rendered() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{this.stack.map((banner) => {
|
||||||
|
const Component = this.components[banner.type];
|
||||||
|
if (!Component) return null;
|
||||||
|
return (
|
||||||
|
<AnimatePresence>
|
||||||
|
<Container
|
||||||
|
variants={{
|
||||||
|
show: {
|
||||||
|
// slide down
|
||||||
|
y: 0,
|
||||||
|
transition: {
|
||||||
|
delayChildren: 0.3,
|
||||||
|
staggerChildren: 0.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hide: {
|
||||||
|
// slide up
|
||||||
|
y: "-100%",
|
||||||
|
transition: {
|
||||||
|
delayChildren: 0.3,
|
||||||
|
staggerChildren: 0.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
initial="hide"
|
||||||
|
animate="show"
|
||||||
|
exit="hide"
|
||||||
|
onAnimationComplete={() => {
|
||||||
|
console.debug("animation complete");
|
||||||
|
}}
|
||||||
|
//style={bannerContext.content.style}
|
||||||
|
>
|
||||||
|
<Component {...banner} onClose={() => this.remove(banner.key!)} />
|
||||||
|
{/* {!bannerContext.content.forced && (
|
||||||
|
<CloseWrapper
|
||||||
|
onClick={() => {
|
||||||
|
bannerContext.close();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon icon="mdiClose" color="var(--text)" size="24px" />
|
||||||
|
</CloseWrapper>
|
||||||
|
)} */}
|
||||||
|
</Container>
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a banner is currently visible
|
||||||
|
*/
|
||||||
|
get isVisible() {
|
||||||
|
return this.stack.length > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const bannerController = new BannerController({
|
||||||
|
offline: OfflineBanner,
|
||||||
|
});
|
6
src/controllers/banners/BannerRenderer.tsx
Normal file
6
src/controllers/banners/BannerRenderer.tsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { bannerController } from ".";
|
||||||
|
|
||||||
|
export default observer(() => {
|
||||||
|
return <>{bannerController.rendered}</>;
|
||||||
|
});
|
3
src/controllers/banners/index.ts
Normal file
3
src/controllers/banners/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./BannerController";
|
||||||
|
export * from "./BannerRenderer";
|
||||||
|
export * from "./types";
|
9
src/controllers/banners/types.ts
Normal file
9
src/controllers/banners/types.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export type Banner = {
|
||||||
|
key?: string;
|
||||||
|
} & {
|
||||||
|
type: "offline";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BannerProps<T extends Banner["type"]> = Banner & { type: T } & {
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
@ -1,34 +1,30 @@
|
|||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#root,
|
#root {
|
||||||
#root > div {
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
h1,
|
display: flex;
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6,
|
|
||||||
p,
|
|
||||||
span,
|
|
||||||
textarea {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html *:not(code) {
|
|
||||||
font-family: var(--font-family);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
overflow: hidden;
|
/* overflow: hidden; */
|
||||||
|
font-family: var(--font-family);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:after,
|
||||||
|
*:before {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* html *:not(code) {
|
||||||
|
font-family: var(--font-family);
|
||||||
|
} */
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-family: "Source Code Pro", monospace;
|
font-family: "Source Code Pro", monospace;
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import ReactDOM from "react-dom/client";
|
|||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import { ErrorBoundaryContext } from "react-use-error-boundary";
|
import { ErrorBoundaryContext } from "react-use-error-boundary";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import { BannerContextProvider } from "./contexts/BannerContext";
|
|
||||||
import { ContextMenuContextProvider } from "./contexts/ContextMenuContext";
|
import { ContextMenuContextProvider } from "./contexts/ContextMenuContext";
|
||||||
import Theme from "./contexts/Theme";
|
import Theme from "./contexts/Theme";
|
||||||
import ModalRenderer from "./controllers/modals/ModalRenderer";
|
import ModalRenderer from "./controllers/modals/ModalRenderer";
|
||||||
@ -32,12 +31,10 @@ dayjs.extend(calendar, calendarStrings);
|
|||||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
<ErrorBoundaryContext>
|
<ErrorBoundaryContext>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<BannerContextProvider>
|
<ContextMenuContextProvider>
|
||||||
<ContextMenuContextProvider>
|
<App />
|
||||||
<App />
|
<ModalRenderer />
|
||||||
<ModalRenderer />
|
</ContextMenuContextProvider>
|
||||||
</ContextMenuContextProvider>
|
|
||||||
</BannerContextProvider>
|
|
||||||
<Theme />
|
<Theme />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</ErrorBoundaryContext>,
|
</ErrorBoundaryContext>,
|
||||||
|
@ -11,8 +11,8 @@ const Wrapper = styled.div`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100vh;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SpacebarLogo = styled(SpacebarLogoBlue)`
|
const SpacebarLogo = styled(SpacebarLogoBlue)`
|
||||||
@ -29,7 +29,11 @@ function LoadingPage() {
|
|||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<SpacebarLogo />
|
<SpacebarLogo />
|
||||||
<PulseLoader color="var(--text)" />
|
<PulseLoader color="var(--text)" />
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { isMobile } from "react-device-detect";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import Banner from "../../components/Banner";
|
|
||||||
import ChannelSidebar from "../../components/ChannelSidebar";
|
import ChannelSidebar from "../../components/ChannelSidebar";
|
||||||
import ContainerComponent from "../../components/Container";
|
import ContainerComponent from "../../components/Container";
|
||||||
import ErrorBoundary from "../../components/ErrorBoundary";
|
import ErrorBoundary from "../../components/ErrorBoundary";
|
||||||
import GuildSidebar from "../../components/GuildSidebar";
|
import GuildSidebar from "../../components/GuildSidebar";
|
||||||
|
import SwipeableLayout from "../../components/SwipeableLayout";
|
||||||
import Chat from "../../components/messaging/Chat";
|
import Chat from "../../components/messaging/Chat";
|
||||||
|
import BannerRenderer from "../../controllers/banners/BannerRenderer";
|
||||||
import { useAppStore } from "../../stores/AppStore";
|
import { useAppStore } from "../../stores/AppStore";
|
||||||
|
|
||||||
const Container = styled(ContainerComponent)`
|
const Container = styled(ContainerComponent)`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -22,6 +25,24 @@ const Wrapper = styled.div`
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
function LeftPanel() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<GuildSidebar />
|
||||||
|
<ChannelSidebar />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RightPanel() {
|
||||||
|
return <div style={{ height: "100%", backgroundColor: "green", color: "white" }}>Right Panel</div>;
|
||||||
|
}
|
||||||
|
|
||||||
function ChannelPage() {
|
function ChannelPage() {
|
||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
|
|
||||||
@ -32,9 +53,22 @@ function ChannelPage() {
|
|||||||
app.setActiveChannelId(channelId);
|
app.setActiveChannelId(channelId);
|
||||||
}, [guildId, channelId]);
|
}, [guildId, channelId]);
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<BannerRenderer />
|
||||||
|
<SwipeableLayout leftChildren={<LeftPanel />} rightChildren={<RightPanel />}>
|
||||||
|
<ErrorBoundary section="component">
|
||||||
|
<Chat />
|
||||||
|
</ErrorBoundary>
|
||||||
|
</SwipeableLayout>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Banner />
|
<BannerRenderer />
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<GuildSidebar />
|
<GuildSidebar />
|
||||||
<ChannelSidebar />
|
<ChannelSidebar />
|
||||||
|
@ -31,7 +31,6 @@ export default class AppStore {
|
|||||||
// whether the app is still loading
|
// whether the app is still loading
|
||||||
@observable isAppLoading = true;
|
@observable isAppLoading = true;
|
||||||
|
|
||||||
@observable isNetworkConnected = true;
|
|
||||||
@observable tokenLoaded = false;
|
@observable tokenLoaded = false;
|
||||||
@observable token: string | null = null;
|
@observable token: string | null = null;
|
||||||
@observable fpsShown: boolean = process.env.NODE_ENV === "development";
|
@observable fpsShown: boolean = process.env.NODE_ENV === "development";
|
||||||
@ -69,9 +68,6 @@ export default class AppStore {
|
|||||||
// bind this in windowToggleFps
|
// bind this in windowToggleFps
|
||||||
this.windowToggleFps = this.windowToggleFps.bind(this);
|
this.windowToggleFps = this.windowToggleFps.bind(this);
|
||||||
window.windowToggleFps = this.windowToggleFps;
|
window.windowToggleFps = this.windowToggleFps;
|
||||||
|
|
||||||
window.addEventListener("online", () => this.setNetworkConnected(true));
|
|
||||||
window.addEventListener("offline", () => this.setNetworkConnected(false));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -89,17 +85,12 @@ export default class AppStore {
|
|||||||
this.account = new AccountStore(user);
|
this.account = new AccountStore(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
|
||||||
setNetworkConnected(value: boolean) {
|
|
||||||
this.isNetworkConnected = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
/**
|
/**
|
||||||
* Whether the app is done loading and ready to be displayed
|
* Whether the app is done loading and ready to be displayed
|
||||||
*/
|
*/
|
||||||
get isReady() {
|
get isReady() {
|
||||||
return !this.isAppLoading && this.isGatewayReady && this.isNetworkConnected;
|
return !this.isAppLoading && this.isGatewayReady;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
Loading…
Reference in New Issue
Block a user