Merge branch 'cordova'
commit
6b496cc045
|
|
@ -20,12 +20,15 @@
|
|||
],
|
||||
"ignorePatterns": [
|
||||
"node_modules/**",
|
||||
"**/dist/**"
|
||||
"**/dist/**",
|
||||
"cordova/**",
|
||||
"scripts/**"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"no-undef": "off",
|
||||
|
||||
/**
|
||||
* Having a semicolon helps the optimizer interpret your code correctly.
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
import {resolve, sep} from 'path';
|
||||
|
||||
export default {
|
||||
'*.{js,ts,tsx}': 'eslint --cache --fix',
|
||||
|
||||
/**
|
||||
* Run typechecking if any type-sensitive files was staged
|
||||
* @param {string[]} filenames
|
||||
* @return {string[]}
|
||||
*/
|
||||
'packages/**/{*.ts,*.vue,tsconfig.json}': ({filenames}) => {
|
||||
const pathToPackages = resolve(process.cwd(), 'packages') + sep;
|
||||
return Array.from(
|
||||
filenames.reduce((set, filename) => {
|
||||
const pack = filename.replace(pathToPackages, '').split(sep)[0];
|
||||
set.add(`npm run typecheck:${pack} --if-present`);
|
||||
return set;
|
||||
}, new Set),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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
|
||||
#
|
||||
# http://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.
|
||||
|
||||
.DS_Store
|
||||
|
||||
# Generated by package manager
|
||||
node_modules/
|
||||
|
||||
# Generated by Cordova
|
||||
/plugins/
|
||||
/platforms/
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<widget id="io.cordova.hellocordova" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||
<name>HelloCordova</name>
|
||||
<description>Sample Apache Cordova App</description>
|
||||
<author email="dev@cordova.apache.org" href="https://cordova.apache.org">
|
||||
Apache Cordova Team
|
||||
</author>
|
||||
<content src="index.html" />
|
||||
<allow-intent href="http://*/*" />
|
||||
<allow-intent href="https://*/*" />
|
||||
</widget>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "io.cordova.hellocordova",
|
||||
"displayName": "HelloCordova",
|
||||
"version": "1.0.0",
|
||||
"description": "A sample Apache Cordova application that responds to the deviceready event.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"cordova": "cordova"
|
||||
},
|
||||
"keywords": [
|
||||
"ecosystem:cordova"
|
||||
],
|
||||
"author": "Apache Cordova Team",
|
||||
"license": "Apache-2.0",
|
||||
"cordova": {
|
||||
"platforms": [
|
||||
"android"
|
||||
],
|
||||
"plugins": {
|
||||
"cordova-plugin-qrscanner": {}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"cordova": "^11.0.0",
|
||||
"cordova-plugin-qrscanner-mm": "^4.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cordova-android": "^10.1.2"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<!--
|
||||
Customize this policy to fit your own app's needs. For more guidance, please refer to the docs:
|
||||
https://cordova.apache.org/docs/en/latest/
|
||||
Some notes:
|
||||
* https://ssl.gstatic.com is required only on Android and is needed for TalkBack to function properly
|
||||
* Disables use of inline scripts in order to mitigate risk of XSS vulnerabilities. To change this:
|
||||
* Enable inline JS: add 'unsafe-inline' to default-src
|
||||
-->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:; connect-src *;">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="msapplication-tap-highlight" content="no">
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<title>Hello World</title>
|
||||
<style>
|
||||
html, body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
body {
|
||||
position: relative
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="margin: 0px; overflow: hidden;">
|
||||
<div id="app" style="width: 100vw; height: 100vh;"></div>
|
||||
|
||||
<script src="cordova.js"></script>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
document.addEventListener('deviceready', onDeviceReady, false);
|
||||
|
||||
function onDeviceReady() {
|
||||
// alert(cordova.platformId + " " + cordova.version);
|
||||
|
||||
var my_awesome_script = document.createElement('script');
|
||||
my_awesome_script.setAttribute('src', 'app.js');
|
||||
my_awesome_script.setAttribute('type', 'module');
|
||||
my_awesome_script.setAttribute('crossorigin', true);
|
||||
|
||||
document.head.appendChild(my_awesome_script);
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -32,7 +32,8 @@
|
|||
"typecheck:renderer": "vue-tsc --noEmit -p packages/renderer/tsconfig.json",
|
||||
"typecheck:server": "tsc --noEmit -p packages/server/tsconfig.json",
|
||||
"typecheck": "npm run typecheck:main && npm run typecheck:preload && npm run typecheck:renderer && npm run typecheck:server",
|
||||
"postinstall": "cross-env ELECTRON_RUN_AS_NODE=1 npx --no-install electron ./scripts/update-electron-vendors.js "
|
||||
"postinstall": "cross-env ELECTRON_RUN_AS_NODE=1 npx --no-install electron ./scripts/update-electron-vendors.js ",
|
||||
"cordova": "cordova"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "5.30.6",
|
||||
|
|
@ -56,19 +57,23 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@types/mysql": "^2.15.21",
|
||||
"@types/qrcode": "^1.4.2",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-timeago": "^4.1.3",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/ws": "^8.5.3",
|
||||
"@vitejs/plugin-react": "^2.0.0",
|
||||
"cordova": "^11.0.0",
|
||||
"electron-updater": "5.0.5",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"express": "^4.18.1",
|
||||
"get-port": "^6.1.2",
|
||||
"mysql": "^2.18.1",
|
||||
"qrcode": "^1.5.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.4.0",
|
||||
"react-time-ago": "^7.2.1",
|
||||
"react-timeago": "^7.1.0",
|
||||
"uuid": "^8.3.2",
|
||||
|
|
|
|||
|
|
@ -2,5 +2,10 @@
|
|||
* @module preload
|
||||
*/
|
||||
|
||||
export { getClientId, setClientId } from './clientId';
|
||||
export {
|
||||
getClientId,
|
||||
setClientId,
|
||||
getHomeServer,
|
||||
setHomeServer,
|
||||
} from './settings';
|
||||
export {versions} from './versions';
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
mkdirSync,
|
||||
existsSync,
|
||||
} from 'fs';
|
||||
import { URL } from 'url';
|
||||
|
||||
const appdataPath = process.env.APPDATA || // windows
|
||||
(process.platform == 'darwin' ?
|
||||
|
|
@ -13,6 +14,7 @@ const appdataPath = process.env.APPDATA || // windows
|
|||
|
||||
const cornerDataPath = resolve(appdataPath, 'corner');
|
||||
const clientIdPath = resolve(cornerDataPath, 'clientId');
|
||||
const homeServerPath = resolve(cornerDataPath, 'homeServer');
|
||||
|
||||
// --- setup ---
|
||||
|
||||
|
|
@ -39,4 +41,22 @@ export function setClientId(id: string) {
|
|||
if(!validUuid(id)) return false;
|
||||
writeFileSync(clientIdPath, id);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getHomeServer() {
|
||||
const url = readFileSync(homeServerPath).toString()
|
||||
try {
|
||||
new URL(url);
|
||||
return url;
|
||||
} catch(e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function setHomeServer(url: string) {
|
||||
if(url === null) {
|
||||
writeFileSync(homeServerPath, '');
|
||||
return null
|
||||
}
|
||||
writeFileSync(homeServerPath, url);
|
||||
}
|
||||
|
|
@ -6,8 +6,8 @@
|
|||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body style="margin: 0px; overflow: hidden; color: #f8f8f2; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif">
|
||||
<div id="app" style="width: 100vw; height: 100vh; background: #282a36"></div>
|
||||
<body style=" margin: 0px; overflow: hidden;">
|
||||
<div id="app" style="width: 100vw; height: 100vh;"></div>
|
||||
<script src="./src/index.tsx" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import useMediaQuery from '../lib/useMediaQueries';
|
||||
|
||||
|
||||
|
||||
export default function Sidebar(props: {
|
||||
threshold: number,
|
||||
sidebar: number,
|
||||
children: any[]
|
||||
}) {
|
||||
const bigScreen = useMediaQuery('(min-width:' + props.threshold + 'px)');
|
||||
const [screenRef, setScreenRef] = useState<HTMLDivElement | null>(null);
|
||||
const [startDrag, setStartDrag] = useState(0);
|
||||
const [currentDrag, setCurrentDrag] = useState(0);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const [opened, setOpened] = useState(false);
|
||||
|
||||
const difference = opened ?
|
||||
Math.min(currentDrag - startDrag, 0) :
|
||||
Math.max(currentDrag - startDrag, 0);
|
||||
|
||||
const pointerDown = useCallback((e: any) => {
|
||||
setDragging(true);
|
||||
setStartDrag(e.touches[0].clientX);
|
||||
setCurrentDrag(e.touches[0].clientX);
|
||||
}, [dragging, startDrag, currentDrag]);
|
||||
|
||||
const pointerUp = useCallback(() => {
|
||||
setDragging(false);
|
||||
if(difference > 0) {
|
||||
setOpened(true);
|
||||
} else if (difference < 0) {
|
||||
setOpened(false);
|
||||
}
|
||||
}, [dragging, currentDrag, startDrag, opened]);
|
||||
|
||||
const pointerMove = useCallback((e: any) => {
|
||||
setCurrentDrag(e.touches[0].clientX);
|
||||
}, [dragging, currentDrag]);
|
||||
|
||||
useEffect(() => {
|
||||
if(screenRef === null) return;
|
||||
screenRef.addEventListener('touchstart', pointerDown);
|
||||
screenRef.addEventListener('touchend', pointerUp);
|
||||
screenRef.addEventListener('touchmove', pointerMove);
|
||||
// screenRef.addEventListener('pointercancel', pointerUp);
|
||||
return () => {
|
||||
screenRef.removeEventListener('touchstart', pointerDown);
|
||||
screenRef.removeEventListener('touchend', pointerUp);
|
||||
screenRef.removeEventListener('touchmove', pointerMove);
|
||||
// screenRef.removeEventListener('pointercancel', pointerUp);
|
||||
};
|
||||
}, [screenRef, pointerUp, pointerDown]);
|
||||
|
||||
return <div ref={setScreenRef} style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
userSelect: 'none',
|
||||
// overflow: 'hidden',
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
// background: 'red',
|
||||
width: bigScreen ? (props.sidebar + 'px') : '100%',
|
||||
height: '100%',
|
||||
display: 'inline-block',
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
left: bigScreen ? '0px' : !dragging ? (opened ? '0px' : '-100%') : `calc(${difference}px ${opened ? '' : '- 100%'})`,
|
||||
zIndex: '1',
|
||||
transition: dragging ? 'none' : 'left 300ms linear, width 300ms linear',
|
||||
}}
|
||||
>{props.children[0]}</div>
|
||||
<div
|
||||
style={{
|
||||
// background: 'green',
|
||||
width: bigScreen ? 'calc(100% - ' + props.sidebar + 'px)' : '100%',
|
||||
height: '100%',
|
||||
display: 'inline-block',
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
left: bigScreen ? (props.sidebar + 'px') : '0px',
|
||||
zIndex: '0',
|
||||
transition: 'left 300ms linear, width 300ms linear',
|
||||
}}
|
||||
>{props.children[1]}</div>
|
||||
</div>;
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { createContext } from 'react';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Sidebar from './components/Sidebar';
|
||||
import App from './pages/App';
|
||||
|
||||
ReactDOM.render(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
import * as preload from '#preload';
|
||||
|
||||
console.log('#preload', preload);
|
||||
|
||||
const functions: any = (function() {
|
||||
const electron = !!preload.getClientId;
|
||||
const cordova = 'cordova' in globalThis;
|
||||
|
||||
console.log(preload);
|
||||
|
||||
// alert('Electron: ' + electron + '\nCordova: ' + cordova);
|
||||
|
||||
if(electron) {
|
||||
return preload;
|
||||
} else {
|
||||
let cid: string | null = null;
|
||||
let homeServer: string | null = null;
|
||||
return {
|
||||
getClientId() {
|
||||
return cid;
|
||||
},
|
||||
setClientId(id: any) {
|
||||
cid = id;
|
||||
},
|
||||
getHomeServer() {
|
||||
return homeServer;
|
||||
},
|
||||
setHomeServer(str: string) {
|
||||
homeServer = str;
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
console.log('native functions loaded', functions);
|
||||
|
||||
export const getClientId = functions.getClientId;
|
||||
export const setClientId = functions.setClientId;
|
||||
export const getHomeServer = functions.getHomeServer;
|
||||
export const setHomeServer = functions.setHomeServer;
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
|
||||
function useMediaQuery(query: string): boolean {
|
||||
const getMatches = (query: string): boolean => {
|
||||
// Prevents SSR issues
|
||||
if (typeof window !== 'undefined') {
|
||||
return window.matchMedia(query).matches;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const [matches, setMatches] = useState<boolean>(getMatches(query));
|
||||
|
||||
function handleChange() {
|
||||
setMatches(getMatches(query));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const matchMedia = window.matchMedia(query);
|
||||
|
||||
// Triggered at the first client-side load and if query changes
|
||||
handleChange();
|
||||
|
||||
// Listen matchMedia
|
||||
if (matchMedia.addListener) {
|
||||
matchMedia.addListener(handleChange);
|
||||
} else {
|
||||
matchMedia.addEventListener('change', handleChange);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (matchMedia.removeListener) {
|
||||
matchMedia.removeListener(handleChange);
|
||||
} else {
|
||||
matchMedia.removeEventListener('change', handleChange);
|
||||
}
|
||||
};
|
||||
}, [query]);
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
export default useMediaQuery;
|
||||
|
|
@ -1,24 +1,60 @@
|
|||
import { createContext, useEffect, useState } from 'react';
|
||||
import { createContext, useCallback, useEffect, useState, useMemo } from 'react';
|
||||
import Channels from './Channels';
|
||||
import Chat from './Chat';
|
||||
import { getClientId, setClientId } from '#preload';
|
||||
import {
|
||||
getClientId,
|
||||
setClientId,
|
||||
getHomeServer,
|
||||
setHomeServer
|
||||
} from '../lib/native';
|
||||
import { useApi } from '../lib/useApi';
|
||||
import Sidebar from '../components/Sidebar';
|
||||
import NewAccount from './NewAccount';
|
||||
|
||||
export const channelContext = createContext<{
|
||||
export const ChannelContext = createContext<{
|
||||
channel: string | null,
|
||||
setChannel: (uid: string) => void
|
||||
}>({
|
||||
channel: null,
|
||||
setChannel: () => {},
|
||||
});
|
||||
|
||||
export const clientIdContext = createContext<string | null>(null);
|
||||
export const ClientIdContext = createContext<{
|
||||
clientId: string | null,
|
||||
setClientId: (id: string | null) => void
|
||||
}>({
|
||||
clientId: null,
|
||||
setClientId: () => {}
|
||||
});
|
||||
export const HomeServerContext = createContext<{
|
||||
homeServer: string | null,
|
||||
setHomeServer: (uid: string | null) => void
|
||||
}>({
|
||||
homeServer: null,
|
||||
setHomeServer: () => {}
|
||||
});
|
||||
export const TransparencyContext = createContext<(transparent: boolean) => void>(() => {});
|
||||
|
||||
export default function App() {
|
||||
const [channel, setChannel] = useState<string | null>(null);
|
||||
const [clientId, setCachedClientId] = useState(getClientId());
|
||||
const channelContextValue = { channel, setChannel };
|
||||
|
||||
const [homeServer, setCachedHomeServer] = useState<string | null>(getHomeServer());
|
||||
const channelContextValue = { channel, setChannel }
|
||||
|
||||
const [transparent, setTransparent] = useState(false);
|
||||
|
||||
const setHomeServerCallback = useCallback((url: string | null) => {
|
||||
console.log('SETTING HOME SERVER', url)
|
||||
setHomeServer(url);
|
||||
setCachedHomeServer(getHomeServer());
|
||||
}, [homeServer]);
|
||||
|
||||
const homeServerContextValue = useMemo(() => {
|
||||
return {
|
||||
homeServer,
|
||||
setHomeServer: setHomeServerCallback
|
||||
};
|
||||
}, [homeServer, setHomeServerCallback])
|
||||
|
||||
// persist given clientId to disk
|
||||
useEffect(() => {
|
||||
if(clientId === null) return;
|
||||
|
|
@ -36,26 +72,48 @@ export default function App() {
|
|||
send('client:new');
|
||||
}, [clientId]);
|
||||
|
||||
const clientIdContextValue = { clientId, setClientId: setCachedClientId };
|
||||
|
||||
// font-size: 16px;
|
||||
// font-family: 'Lato', sans-serif;
|
||||
// font-family: 'Red Hat Text', sans-serif;
|
||||
// font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
// color: #f8f8f2;
|
||||
// background: #282a36;
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<clientIdContext.Provider value={clientId}>
|
||||
<channelContext.Provider value={channelContextValue}>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '200px 1fr',
|
||||
gridTemplateRows: '1fr',
|
||||
height: '100%',
|
||||
}}>
|
||||
<div style={{
|
||||
background: '#21222c',
|
||||
borderRight: '1px solid #bd93f9',
|
||||
}}>
|
||||
<Channels></Channels>
|
||||
</div>
|
||||
<div>
|
||||
<Chat></Chat>
|
||||
</div>
|
||||
</div>
|
||||
</channelContext.Provider>
|
||||
</clientIdContext.Provider>
|
||||
<ClientIdContext.Provider value={clientIdContextValue}>
|
||||
<ChannelContext.Provider value={channelContextValue}>
|
||||
<HomeServerContext.Provider value={homeServerContextValue}>
|
||||
<TransparencyContext.Provider value={setTransparent}>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin='' />
|
||||
<link href={"https://fonts.googleapis.com/css2?family=Fira+Sans&family=Josefin+Sans&family=Lato&family=Radio+Canada&family=Readex+Pro&family=Red+Hat+Text&family=Rubik&family=Signika&family=Telex&display=swap"} rel="stylesheet" />
|
||||
<div style={{
|
||||
background: transparent ? 'rgba(0, 0, 0, 0)' : '#282a36',
|
||||
color: transparent ? 'black' : '#f8f8f2',
|
||||
fontSize: '16px',
|
||||
fontFamily: "'Red Hat Text', sans-serif",
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}}>
|
||||
{homeServer === null && (
|
||||
<NewAccount></NewAccount>
|
||||
) || (
|
||||
<Sidebar
|
||||
threshold={800}
|
||||
sidebar={300}
|
||||
>
|
||||
<Channels></Channels>
|
||||
<Chat></Chat>
|
||||
</Sidebar>
|
||||
)}
|
||||
</div>
|
||||
</TransparencyContext.Provider>
|
||||
</HomeServerContext.Provider>
|
||||
</ChannelContext.Provider>
|
||||
</ClientIdContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { channelContext, clientIdContext } from './App';
|
||||
import { ChannelContext, ClientIdContext, HomeServerContext } from './App';
|
||||
import { useApi } from '../lib/useApi';
|
||||
import type { IMessage } from './Message';
|
||||
import NameTextbox from './NameTextbox';
|
||||
import LoginQR from './LoginQR';
|
||||
|
||||
interface IChannel {
|
||||
uid: string;
|
||||
|
|
@ -26,8 +27,10 @@ export default function Channels() {
|
|||
const [channels, setChannels] = useState<IChannel[]>([]);
|
||||
const [unreads, setUnreads] = useState<IUnreads>({});
|
||||
|
||||
const {channel, setChannel} = useContext(channelContext);
|
||||
const clientId = useContext(clientIdContext);
|
||||
const { channel, setChannel } = useContext(ChannelContext);
|
||||
const { clientId } = useContext(ClientIdContext);
|
||||
|
||||
const { setHomeServer } = useContext(HomeServerContext);
|
||||
|
||||
const { send } = useApi({
|
||||
'channels:list'(data: IChannel[]) {
|
||||
|
|
@ -56,8 +59,10 @@ export default function Channels() {
|
|||
}, [channels]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(channel, channels);
|
||||
if(channels.length === 0) return;
|
||||
if(channel !== null) return;
|
||||
console.log('this is what setChannel is', setChannel);
|
||||
setChannel(channels[0].uid);
|
||||
}, [channel, channels]);
|
||||
|
||||
|
|
@ -83,7 +88,10 @@ export default function Channels() {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{
|
||||
height: '100%',
|
||||
background: '#21222c',
|
||||
}}>
|
||||
<br></br>
|
||||
{channels.map(c => (
|
||||
<div key={c.uid} style={{
|
||||
|
|
@ -132,7 +140,9 @@ export default function Channels() {
|
|||
borderRadius: '8px',
|
||||
// lineHeight: '20px'
|
||||
}}>ADD</button>
|
||||
<NameTextbox></NameTextbox>
|
||||
</>
|
||||
<NameTextbox></NameTextbox><br></br>
|
||||
<button onClick={() => setHomeServer(null)}>leave</button><br></br>
|
||||
<LoginQR></LoginQR>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
import { useApi } from '../lib/useApi';
|
||||
import { channelContext, clientIdContext } from './App';
|
||||
import { ChannelContext, ClientIdContext } from './App';
|
||||
import type { IMessage} from './Message';
|
||||
import { Message } from './Message';
|
||||
import { MdSend } from 'react-icons/md'
|
||||
|
||||
function createMessage(from: string, text: string,
|
||||
channel: string, t = 0): IMessage {
|
||||
|
|
@ -20,10 +21,13 @@ export default () => {
|
|||
const [messages, setMessages] = useState<IMessage[]>([]);
|
||||
const [hist, setHist] = useState(false);
|
||||
|
||||
const CHATBOX_SIZE = 64;
|
||||
const PADDING = 8;
|
||||
|
||||
const textBoxRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { channel, setChannel } = useContext(channelContext);
|
||||
const clientId = useContext(clientIdContext);
|
||||
const { channel, setChannel } = useContext(ChannelContext);
|
||||
const { clientId } = useContext(ClientIdContext);
|
||||
|
||||
const { send } = useApi({
|
||||
'message:message'(data: IMessage) {
|
||||
|
|
@ -63,83 +67,84 @@ export default () => {
|
|||
}, [sendMessage]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 64px',
|
||||
gridTemplateRows: '1fr 64px',
|
||||
gridTemplateAreas: '"content content" "message send"',
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
// borderBottom: '1px solid #bd93f9',
|
||||
gridArea: 'content',
|
||||
position: 'relative',
|
||||
}}>
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: '0px',
|
||||
width: '100%',
|
||||
}}>
|
||||
{messages.map(message => (
|
||||
<Message key={message.uid} message={message}></Message>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div onClick={() => {
|
||||
textBoxRef.current?.focus();
|
||||
}}style={{
|
||||
margin: '8px',
|
||||
marginRight: '3px',
|
||||
borderRadius: '8px',
|
||||
background: '#343746',
|
||||
gridArea: 'message',
|
||||
display: 'grid',
|
||||
placeItems: 'center center',
|
||||
padding: '0px 16px',
|
||||
cursor: 'text',
|
||||
overflow: 'auto',
|
||||
}}>
|
||||
<div
|
||||
ref={textBoxRef}
|
||||
onKeyDown={keyDown}
|
||||
className="input"
|
||||
role="textbox"
|
||||
contentEditable
|
||||
style={{
|
||||
background: 'inherit',
|
||||
outline: 'none',
|
||||
boxSizing: 'border-box',
|
||||
borderRadius: '8px',
|
||||
width: '100%',
|
||||
resize: 'none',
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: `1fr ${CHATBOX_SIZE}px`,
|
||||
gridTemplateRows: `1fr ${CHATBOX_SIZE}px`,
|
||||
gridTemplateAreas: '"content content" "message send"',
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
// borderBottom: '1px solid #bd93f9',
|
||||
gridArea: 'content',
|
||||
position: 'relative',
|
||||
// borderBottom: '1px solid white'
|
||||
}}>
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: '0px',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: '8px',
|
||||
boxSizing: 'border-box',
|
||||
}}>
|
||||
<div onClick={sendMessage} style={{
|
||||
background: '#bd93f9',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
// borderRadius: '50%',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
display: 'grid',
|
||||
placeItems: 'center center',
|
||||
fontSize: '32px',
|
||||
}}>
|
||||
|
||||
</div>
|
||||
{messages.map(message => (
|
||||
<Message key={message.uid} message={message}></Message>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<div onClick={() => {
|
||||
textBoxRef.current?.focus();
|
||||
}}style={{
|
||||
margin: PADDING + 'px',
|
||||
marginRight: '0px',
|
||||
borderRadius: ((CHATBOX_SIZE - PADDING*2) / 2) + 'px',
|
||||
background: '#343746',
|
||||
gridArea: 'message',
|
||||
display: 'grid',
|
||||
placeItems: 'center center',
|
||||
padding: '0px 16px',
|
||||
cursor: 'text',
|
||||
overflow: 'auto',
|
||||
}}>
|
||||
<div
|
||||
ref={textBoxRef}
|
||||
onKeyDown={keyDown}
|
||||
className="input"
|
||||
role="textbox"
|
||||
contentEditable
|
||||
style={{
|
||||
background: 'inherit',
|
||||
outline: 'none',
|
||||
boxSizing: 'border-box',
|
||||
// borderRadius: '8px',
|
||||
// borderRadius: '50%',
|
||||
width: '100%',
|
||||
resize: 'none',
|
||||
// border: '1px solid white',
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: '8px',
|
||||
boxSizing: 'border-box',
|
||||
}}>
|
||||
<div onClick={sendMessage} style={{
|
||||
background: '#bd93f9',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: '50%',
|
||||
// borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
display: 'grid',
|
||||
placeItems: 'center center',
|
||||
fontSize: '32px',
|
||||
}}>
|
||||
<MdSend></MdSend>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { useContext, useEffect, useState } from "react";
|
||||
import { ClientIdContext, HomeServerContext } from "./App";
|
||||
import QR from 'qrcode';
|
||||
|
||||
export default function() {
|
||||
const { homeServer } = useContext(HomeServerContext);
|
||||
const { clientId } = useContext(ClientIdContext);
|
||||
const [qr, setQr] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setQr(await QR.toDataURL(
|
||||
'loginv1|' + homeServer + '|' + clientId
|
||||
));
|
||||
})()
|
||||
}, [clientId, homeServer])
|
||||
|
||||
return <img src={qr ?? undefined} />
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ export function Message({
|
|||
return (
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '128px 1fr',
|
||||
gridTemplateColumns: '4em 1fr',
|
||||
width: '100%',
|
||||
padding: '1px 0px',
|
||||
}}>
|
||||
|
|
@ -37,7 +37,7 @@ export function Message({
|
|||
}}>
|
||||
<TimeAgo
|
||||
date={message.timestamp}
|
||||
formatter={(t, u) => u === 'second' ? 'Just Now' : ('' + t + u[0])}
|
||||
formatter={(t, u) => u === 'second' ? 'Now' : ('' + t + u[0])}
|
||||
></TimeAgo>
|
||||
</span>
|
||||
<span style={{
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ import {
|
|||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { clientIdContext } from './App';
|
||||
import { ClientIdContext } from './App';
|
||||
import { useApi } from '../lib/useApi';
|
||||
|
||||
|
||||
export default function NameTextbox() {
|
||||
const clientId = useContext(clientIdContext);
|
||||
const { clientId } = useContext(ClientIdContext);
|
||||
const [name, setName] = useState<string | null>(null);
|
||||
const [inputElement, setInputElement] = useState<HTMLInputElement | null>(null);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useContext, useRef } from "react"
|
||||
import { ClientIdContext, HomeServerContext, TransparencyContext } from "./App"
|
||||
|
||||
export default function NewAccount() {
|
||||
|
||||
const [data, setData] = useState('');
|
||||
const [scanning, setScanning] = useState(false);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { setHomeServer } = useContext(HomeServerContext);
|
||||
const { setClientId } = useContext(ClientIdContext);
|
||||
|
||||
const setTransparent = useContext(TransparencyContext);
|
||||
|
||||
useEffect(() => {
|
||||
setTransparent(scanning);
|
||||
}, [scanning, setTransparent]);
|
||||
|
||||
const go = useCallback(() => {
|
||||
if(inputRef.current === null) return;
|
||||
setHomeServer(inputRef.current.value)
|
||||
}, [HomeServerContext]);
|
||||
|
||||
const scanQr = useCallback(() => {
|
||||
//@ts-ignore
|
||||
window.QRScanner.prepare((err: any, status: any) => {
|
||||
if(!err && status.authorized) {
|
||||
setScanning(true);
|
||||
//@ts-ignore
|
||||
window.QRScanner.hide();
|
||||
//@ts-ignore
|
||||
window.QRScanner.scan((err, text) => {
|
||||
if (err) return alert(err);
|
||||
// alert(text);
|
||||
setData(text);
|
||||
setScanning(false);
|
||||
//@ts-ignore
|
||||
window.QRScanner.show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
// this avoids a timing issue whereby the component
|
||||
// gets removed before it has a chance to clean up
|
||||
// its setting of transparency...
|
||||
if(scanning) return;
|
||||
if(!data) return;
|
||||
const [action, homeServer, clientId] = data.split('|');
|
||||
switch(action) {
|
||||
case 'loginv1': {
|
||||
setHomeServer(homeServer);
|
||||
setClientId(clientId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [data, scanning])
|
||||
|
||||
return <div style={{
|
||||
display: 'grid',
|
||||
placeContent: 'center center',
|
||||
height: '100%',
|
||||
}}>
|
||||
Create New Account!! <br />
|
||||
Enter Home Server URL <br />
|
||||
<input defaultValue="wss://dev.valnet.xyz" ref={inputRef}></input> <br />
|
||||
<button onClick={go}> GO </button> <br />
|
||||
<br />
|
||||
or scan a QR! <br />
|
||||
<button onClick={scanQr}>SCAN</button><br></br>
|
||||
<pre>
|
||||
{data}
|
||||
{scanning ? 'SCANNING' : 'NOT SCANNING'}
|
||||
</pre>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -40,7 +40,9 @@ const config = {
|
|||
environment: 'happy-dom',
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
react({
|
||||
fastRefresh: false
|
||||
}),
|
||||
renderer.vite({
|
||||
preloadEntry: join(PACKAGE_ROOT, '../preload/src/index.ts'),
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const {execSync} = require('child_process');
|
||||
const { copyFileSync, readdirSync } = require('fs');
|
||||
const { resolve } = require('path');
|
||||
|
||||
execSync('npm run build');
|
||||
|
||||
const files = readdirSync(resolve('./packages/renderer/dist'));
|
||||
|
||||
files.forEach(file => {
|
||||
if(file === 'index.html') return;
|
||||
if(file.endsWith('.js.map'))
|
||||
copyFileSync(resolve('./packages/renderer/dist/', file), './cordova/www/app.js.map');
|
||||
if(file.endsWith('.js'))
|
||||
copyFileSync(resolve('./packages/renderer/dist/', file), './cordova/www/app.js');
|
||||
});
|
||||
|
||||
try {
|
||||
const proc = execSync('npm run cordova run', {
|
||||
cwd: resolve('./cordova'),
|
||||
});
|
||||
} catch(e) {
|
||||
console.log(e.output.toString());
|
||||
}
|
||||
|
||||
// console.log(proc.toString());
|
||||
Reference in New Issue