refactored app so that it works with qapp-core version 1.0.35

This commit is contained in:
2025-06-26 23:05:11 +03:00
parent 8db8fd6c6c
commit f7fd191399
16 changed files with 199 additions and 136 deletions

View File

@@ -1,18 +0,0 @@
module.exports = {
env: { browser: true, amd: true, node: true, es2020: true },
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
],
parser: "@typescript-eslint/parser",
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
plugins: ["react-refresh"],
rules: {
"react-refresh/only-export-components": "warn",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/no-unused-expressions": "off",
"@typescript-eslint/ban-ts-comment": "off",
},
};

37
eslint.config.js Normal file
View File

@@ -0,0 +1,37 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';
import prettierPlugin from 'eslint-plugin-prettier';
import prettierConfig from 'eslint-config-prettier';
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
prettier: prettierPlugin,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'prettier/prettier': 'error',
},
},
{
// This disables ESLint rules that would conflict with Prettier
name: 'prettier-config',
rules: prettierConfig.rules,
}
);

23
package-lock.json generated
View File

@@ -47,6 +47,7 @@
"eslint": "^9.17.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.15.0",
"prettier": "^3.4.2",
"typescript": "^5.7.2",
"vite": "^6.3.5",
@@ -332,6 +333,15 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse/node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/types": {
"version": "7.26.3",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz",
@@ -3344,11 +3354,16 @@
}
},
"node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"version": "15.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
"integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/goober": {

View File

@@ -52,6 +52,7 @@
"prettier": "^3.4.2",
"typescript": "^5.7.2",
"vite": "^6.3.5",
"globals": "^15.15.0",
"vite-plugin-static-copy": "^3.0.0"
}
}

View File

@@ -2,7 +2,6 @@ import { CssBaseline } from "@mui/material";
import { ThemeProvider } from "@mui/material/styles";
import { useEffect, useState } from "react";
import { Provider, useSelector } from "react-redux";
import { Route, Routes } from "react-router-dom";
import { PersistGate } from "redux-persist/integration/react";
import { subscriptionListFilter } from "./App-Functions.ts";
import Notification from "./components/common/Notification/Notification";
@@ -18,13 +17,13 @@ import DownloadWrapper from "./wrappers/DownloadWrapper";
import GlobalWrapper from "./wrappers/GlobalWrapper";
import { ScrollWrapper } from "./wrappers/ScrollWrapper.tsx";
import { QappCoreWrapper } from "./QappCoreWrapper.tsx";
import { Routes } from "./Routes.tsx";
function App() {
// const themeColor = window._qdnTheme
const [theme, setTheme] = useState("dark");
useIframe();
// useIframe();
useEffect(() => {
subscriptionListFilter(false).then(filteredList => {
@@ -35,29 +34,15 @@ function App() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<ThemeProvider theme={theme === "light" ? lightTheme : darkTheme}>
<QappCoreWrapper>
<ThemeProvider theme={darkTheme}>
<CssBaseline />
<Notification />
<DownloadWrapper>
<GlobalWrapper setTheme={(val: string) => setTheme(val)}>
<ScrollWrapper>
<CssBaseline />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/video/:name/:id" element={<VideoContent />} />
<Route
path="/playlist/:name/:id"
element={<PlaylistContent />}
/>
<Route
path="/channel/:name"
element={<IndividualProfile />}
/>
</Routes>
</ScrollWrapper>
</GlobalWrapper>
<Routes />
</DownloadWrapper>
</QappCoreWrapper>
</ThemeProvider>
</PersistGate>
</Provider>

35
src/AppWrapper.tsx Normal file
View File

@@ -0,0 +1,35 @@
import { GlobalProvider } from 'qapp-core'
import { useLocation, useNavigate } from 'react-router-dom'
import Layout from './Layout';
import { useSelector } from 'react-redux';
import { RootState } from './state/store';
import { ScrollWrapper } from './wrappers/ScrollWrapper';
import GlobalWrapper from './wrappers/GlobalWrapper';
export const AppWrapper = () => {
const { user } = useSelector((state: RootState) => state.auth);
return (
<GlobalProvider
config={{
auth: {
authenticateOnMount: false,
userAccountInfo: {
address: user?.address,
publicKey: user?.publicKey
},
},
publicSalt: "usVbeM9YpjGCbLrTcc78YJS0ap1AxDkHAOMZrp3+wDY=",
appName: "Q-Tube",
enableGlobalVideoFeature: true
}}
>
<GlobalWrapper>
<ScrollWrapper>
<Layout />
</ScrollWrapper>
</GlobalWrapper>
</GlobalProvider>
)
}

20
src/Layout.tsx Normal file
View File

@@ -0,0 +1,20 @@
import { Outlet } from 'react-router-dom';
import { useIframe } from './hooks/useIframe';
import { EditVideo } from './components/Publish/EditVideo/EditVideo';
import { EditPlaylist } from './components/Publish/EditPlaylist/EditPlaylist';
const Layout = () => {
useIframe();
return (
<>
{/* Add Header here */}
<main>
<Outlet /> {/* This is where page content will be rendered */}
</main>
{/* Add Footer here */}
</>
);
};
export default Layout;

48
src/Routes.tsx Normal file
View File

@@ -0,0 +1,48 @@
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import App from './App';
import { AppWrapper } from './AppWrapper';
import { Home } from './pages/Home/Home';
import { VideoContent } from './pages/ContentPages/VideoContent/VideoContent';
import { PlaylistContent } from './pages/ContentPages/PlaylistContent/PlaylistContent';
import { IndividualProfile } from './pages/ContentPages/IndividualProfile/IndividualProfile';
interface CustomWindow extends Window {
_qdnBase: string;
}
const customWindow = window as unknown as CustomWindow;
const baseUrl = customWindow?._qdnBase || '';
export function Routes() {
const router = createBrowserRouter(
[
{
path: '/',
element: <AppWrapper />, // GlobalProvider wrapper
children: [
{
index: true,
element: <Home />,
},
{
path: 'video/:name/:id',
element: <VideoContent />,
},
{
path: 'playlist/:name/:id',
element: <PlaylistContent />,
},
{
path: 'channel/:name',
element: <IndividualProfile />,
},
],
},
],
{
basename: baseUrl,
}
);
return <RouterProvider router={router} />;
}

View File

@@ -428,7 +428,7 @@ export const PublishVideo = ({
if (!selectExistingPlaylist) {
throw new Error("select a playlist");
}
// get raw data for playlist
const responseData = await qortalRequest({
action: "FETCH_QDN_RESOURCE",

View File

@@ -41,6 +41,7 @@ export const DownloadTaskManager: React.FC = () => {
const handleCloseDownload = () => {
setAnchorEl(null);
setOpenDownload(false);
};
useEffect(() => {

View File

@@ -95,9 +95,10 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
// <VideoContext.Provider value={contextData}>
<Box sx={{
width: '100%',
height: '70vh'
height: '70vh',
background: 'black'
}}>
<QappVideoPlayer timelineActions={timelineActions} poster={props.poster} videoRef={videoRef} qortalVideoResource={{name: props.name, service: props.service as Service, identifier: props.identifier}} />
<QappVideoPlayer timelineActions={timelineActions} poster={props.poster} videoRef={videoRef} qortalVideoResource={{name: props.name, service: props.service as Service, identifier: props.identifier}} />
</Box>
// <VideoContainer
// tabIndex={0}

68
src/global.d.ts vendored
View File

@@ -1,68 +0,0 @@
// src/global.d.ts
interface Location {
service: string;
name: string;
identifier?: string;
}
interface QortalRequestOptions {
action: string;
name?: string;
service?: string;
data64?: string;
title?: string;
description?: string;
category?: string;
tags?: string[];
identifier?: string;
address?: string;
metaData?: string;
encoding?: string;
includeMetadata?: boolean;
limit?: number;
offset?: number;
reverse?: boolean;
resources?: any[];
filename?: string;
list_name?: string;
item?: string;
items?: strings[];
tag1?: string;
tag2?: string;
tag3?: string;
tag4?: string;
tag5?: string;
coin?: string;
destinationAddress?: string;
amount?: number;
blob?: Blob;
mimeType?: string;
file?: File;
encryptedData?: string;
mode?: string;
query?: string;
excludeBlocked?: boolean;
exactMatchNames?: boolean;
nameListFilter?: string[];
location?: Location;
}
declare function qortalRequest(options: QortalRequestOptions): Promise<any>;
declare function qortalRequestWithTimeout(
options: QortalRequestOptions,
time: number
): Promise<any>;
declare global {
interface Window {
_qdnBase: any; // Replace 'any' with the appropriate type if you know it
_qdnTheme: string;
}
}
declare global {
interface Window {
showSaveFilePicker: (
options?: SaveFilePickerOptions
) => Promise<FileSystemFileHandle>;
}
}

View File

@@ -11,8 +11,8 @@ const customWindow = window as unknown as CustomWindow;
const baseUrl = customWindow?._qdnBase || "";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<BrowserRouter basename={baseUrl}>
<>
<App />
<div id="modal-root" />
</BrowserRouter>
</>
);

View File

@@ -1,4 +1,4 @@
import { Box, Typography, useMediaQuery } from "@mui/material";
import { Box, Button, Typography, useMediaQuery } from "@mui/material";
import React, { useEffect, useState } from "react";
import DeletedVideo from "../../../assets/img/DeletedVideo.jpg";
@@ -66,6 +66,8 @@ export const VideoContent = () => {
});
}, []);
const [newVideo, setNewVideo] = useState(null)
return (
<>
<Box
@@ -78,17 +80,27 @@ export const VideoContent = () => {
}}
onClick={focusVideo}
>
<Button onClick={()=> {
setNewVideo({
identifier: 'MYTEST2_vid_hymn-for-peace_gEpkBT',
name: 'SafetyMongoose',
service: 'VIDEO'
})
}}>play another</Button>
<Button onClick={()=> setNewVideo(null)}>go back</Button>
{videoReference ? (
<VideoPlayerContainer
sx={{
// width: `${videoWidth}%`,
// marginLeft: "0%",
height: '70vh',
backgroundColor: 'black'
}}
>
<VideoPlayer
name={videoReference?.name}
service={videoReference?.service}
identifier={videoReference?.identifier}
name={newVideo?.name || videoReference?.name}
service={newVideo?.service || videoReference?.service}
identifier={newVideo?.identifier || videoReference?.identifier}
user={channelName}
jsonId={id}
poster={videoCover || ""}
@@ -98,17 +110,11 @@ export const VideoContent = () => {
video: { aspectRatio: "16 / 9" },
}}
duration={videoData?.duration}
/>
</VideoPlayerContainer>
) : isVideoLoaded ? (
<img
src={DeletedVideo}
width={"70%"}
height={"37%"}
style={{ marginLeft: "5%" }}
/>
) : (
<Box sx={{ width: "55vw", aspectRatio: "16/9" }}></Box>
<Box sx={{ width: "100%", height: '70vh', background: 'black'}}></Box>
)}
<VideoContentContainer
sx={{ paddingLeft: isScreenSmall ? "5px" : "0px" }}

View File

@@ -36,7 +36,6 @@ import { getPrimaryAccountName } from "../utils/qortalRequestFunctions.ts";
interface Props {
children: React.ReactNode;
setTheme: (val: string) => void;
}
let timer: number | null = null;
@@ -44,7 +43,8 @@ let timer: number | null = null;
export const queue = new RequestQueue();
export const queueSuperlikes = new RequestQueue();
const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
const GlobalWrapper: React.FC<Props> = ({ children }) => {
const [theme, setTheme] = useState('dark')
const dispatch = useDispatch();
const isDragging = useRef(false);
const [userAvatar, setUserAvatar] = useState<string>("");
@@ -210,7 +210,8 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
<>
{isLoadingGlobal && <PageLoader />}
<ConsentModal />
<EditVideo />
<EditPlaylist />
<NavBar
setTheme={(val: string) => setTheme(val)}
isAuthenticated={!!user?.name}
@@ -219,8 +220,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
userAvatar={userAvatar}
authenticate={askForAccountInformation}
/>
<EditVideo />
<EditPlaylist />
{/*<Rnd*/}
{/* onDragStart={onDragStart}*/}
{/* onDragStop={onDragStop}*/}

View File

@@ -20,7 +20,7 @@
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true,
"strictNullChecks": false,
"types": ["node"]
"types": ["node", "qapp-core/global"]
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]