Implemented functionality to add accounts and channels

This commit is contained in:
tyler 2024-03-20 12:36:45 -04:00
parent 08d6bc3782
commit e68567c010
25 changed files with 1536 additions and 43 deletions

128
v1/app.go
View file

@ -67,6 +67,8 @@ func (a *App) startup(ctx context.Context) {
services, err := models.NewServices( services, err := models.NewServices(
models.WithDatabase(db), models.WithDatabase(db),
models.WithAccountService(), models.WithAccountService(),
models.WithChannelService(),
models.WithAccountChannelService(),
) )
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -100,6 +102,84 @@ func (a *App) shutdown(ctx context.Context) {
a.logFileMu.Unlock() a.logFileMu.Unlock()
} }
func (a *App) AddChannel(apiKey string) error {
client := rumblelivestreamlib.Client{StreamKey: apiKey}
resp, err := client.Request()
if err != nil {
a.logError.Println("error executing api request:", err)
return fmt.Errorf("Error querying API. Verify key and try again.")
}
userKey := apiKey
channelKey := ""
if resp.Type == "channel" {
userKey = ""
channelKey = apiKey
}
err = a.addAccountNotExist(resp.UserID, resp.Username, userKey)
if err != nil {
a.logError.Println("error adding account if not exist:", err)
return fmt.Errorf("Error adding channel. Try again.")
}
if resp.Type == "channel" {
err = a.addChannelNotExist(resp.Username, fmt.Sprint(resp.ChannelID), resp.ChannelName, channelKey)
if err != nil {
a.logError.Println("error adding channel if not exist:", err)
return fmt.Errorf("Error adding channel. Try again.")
}
}
return nil
}
func (a *App) addAccountNotExist(uid string, username string, apiKey string) error {
acct, err := a.services.AccountS.ByUsername(username)
if err != nil {
return fmt.Errorf("error querying account by username: %v", err)
}
if acct == nil {
err = a.services.AccountS.Create(&models.Account{
UID: &uid,
Username: &username,
ApiKey: &apiKey,
})
if err != nil {
return fmt.Errorf("error creating account: %v", err)
}
}
return nil
}
func (a *App) addChannelNotExist(username string, cid string, name string, apiKey string) error {
channel, err := a.services.ChannelS.ByName(name)
if err != nil {
return fmt.Errorf("error querying channel by name: %v", err)
}
if channel == nil {
acct, err := a.services.AccountS.ByUsername(username)
if err != nil {
return fmt.Errorf("error querying account by username: %v", err)
}
if acct == nil {
return fmt.Errorf("account does not exist with username: %s", username)
}
err = a.services.ChannelS.Create(&models.Channel{
AccountID: acct.ID,
CID: &cid,
Name: &name,
ApiKey: &apiKey,
})
if err != nil {
return fmt.Errorf("error creating channel: %v", err)
}
}
return nil
}
func (a *App) Login(username string, password string) error { func (a *App) Login(username string, password string) error {
var err error var err error
client, exists := a.clients[username] client, exists := a.clients[username]
@ -133,7 +213,7 @@ func (a *App) Login(username string, password string) error {
return fmt.Errorf("Error logging in. Try again.") return fmt.Errorf("Error logging in. Try again.")
} }
if act == nil { if act == nil {
act = &models.Account{nil, &username, &cookiesS} act = &models.Account{nil, nil, &username, &cookiesS, nil, nil}
err = a.services.AccountS.Create(act) err = a.services.AccountS.Create(act)
if err != nil { if err != nil {
a.logError.Println("error creating account:", err) a.logError.Println("error creating account:", err)
@ -143,10 +223,54 @@ func (a *App) Login(username string, password string) error {
act.Cookies = &cookiesS act.Cookies = &cookiesS
err = a.services.AccountS.Update(act) err = a.services.AccountS.Update(act)
if err != nil { if err != nil {
a.logError.Println("error updating account", err) a.logError.Println("error updating account:", err)
return fmt.Errorf("Error logging in. Try again.") return fmt.Errorf("Error logging in. Try again.")
} }
} }
return nil return nil
} }
func (a *App) SignedIn() (bool, error) {
accounts, err := a.services.AccountS.All()
if err != nil {
a.logError.Println("error getting all accounts:", err)
return false, fmt.Errorf("Error retrieving accounts. Try restarting.")
}
return len(accounts) > 0, nil
}
type Account struct {
Account models.Account `json:"account"`
Channels []models.Channel `json:"channels"`
}
func (a *App) AccountList() (map[string]*Account, error) {
list := map[string]*Account{}
accountChannels, err := a.services.AccountChannelS.All()
if err != nil {
a.logError.Println("error getting all account channels:", err)
return nil, fmt.Errorf("Error retrieving accounts and channels. Try restarting.")
}
for _, ac := range accountChannels {
if ac.Account.Username == nil {
a.logError.Println("account-channel contains nil account username")
return nil, fmt.Errorf("Error retrieving accounts and channels. Try restarting.")
}
act, exists := list[*ac.Account.Username]
if !exists || act == nil {
act = &Account{ac.Account, []models.Channel{}}
list[*ac.Account.Username] = act
}
if ac.Channel.AccountID != nil {
act.Channels = append(act.Channels, ac.Channel)
}
}
return list, nil
}

View file

@ -1,7 +1,8 @@
import { useState } from 'react'; import { useState } from 'react';
import { MemoryRouter as Router, Route, Routes, Link } from 'react-router-dom'; import { MemoryRouter as Router, Route, Routes, Link } from 'react-router-dom';
import './App.css'; import './App.css';
import { NavSignIn } from './Navigation'; import { NavDashboard, NavSignIn } from './Navigation';
import Dashboard from './screens/Dashboard';
import SignIn from './screens/SignIn'; import SignIn from './screens/SignIn';
function App() { function App() {
@ -9,6 +10,7 @@ function App() {
<Router> <Router>
<Routes> <Routes>
<Route path={NavSignIn} element={<SignIn />} /> <Route path={NavSignIn} element={<SignIn />} />
<Route path={NavDashboard} element={<Dashboard />} />
</Routes> </Routes>
</Router> </Router>
); );

View file

@ -1 +1,2 @@
export const NavDashboard = '/dashboard';
export const NavSignIn = '/'; export const NavSignIn = '/';

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -1,9 +1,17 @@
import chevron_right from './icons/chevron-right.png';
import circle_green_background from './icons/circle-green-background.png';
import eye from './icons/eye.png'; import eye from './icons/eye.png';
import eye_slash from './icons/eye-slash.png'; import eye_slash from './icons/eye-slash.png';
import heart from './icons/heart-fill.png';
import plus_circle from './icons/plus-circle-fill.png'
import x_lg from './icons/x-lg.png'; import x_lg from './icons/x-lg.png';
import logo from './logo/logo.png'; import logo from './logo/logo.png';
export const ChevronRight = chevron_right;
export const CircleGreenBackground = circle_green_background;
export const Eye = eye; export const Eye = eye;
export const EyeSlash = eye_slash; export const EyeSlash = eye_slash;
export const Heart = heart;
export const Logo = logo; export const Logo = logo;
export const PlusCircle = plus_circle;
export const XLg = x_lg; export const XLg = x_lg;

View file

@ -0,0 +1,357 @@
.channel-sidebar {
align-items: center;
background-color: #061726;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
padding: 0px 10px;
}
.channel-sidebar-account-list {
border-top: 2px solid #273848;
padding-bottom: 10px;
}
.channel-sidebar-body {
overflow-y: auto;
}
.channel-sidebar-button {
align-items: center;
background-color: #061726;
border: none;
display: flex;
justify-content: center;
padding: 0px;
}
.channel-sidebar-button:hover {
cursor: pointer;
}
.channel-sidebar-button-icon {
height: 60px;
width: 60px;
}
.channel-sidebar-footer {
padding-bottom: 10px;
}
.channel-sidebar-icon {
height: 60px;
margin-top: 10px;
position: relative;
width: 60px;
}
.channel-sidebar-icon-account {
bottom: 0px;
height: 24px;
left: 36px;
position: absolute;
width: 24px;
}
.channel-sidebar-icon-hover {
background-color: #061726;
border-radius: 5px;
color: black;
padding: 10px;
position: fixed;
/* transform: translate(75px, -50px); */
z-index: 10;
}
.channel-sidebar-icon-hover:before {
content:"";
position: absolute;
width: 0;
height: 0;
border-top: 3px solid transparent;
border-right: 3px solid #061726;
border-bottom: 3px solid transparent;
margin: 7px 0 0 -13px;
}
.channel-sidebar-icon-hover-text {
color: white;
font-family: sans-serif;
font-weight: bold;
font-size: 16px;
}
.channel-sidebar-icon-image {
/* border: 3px solid #85c742; */
/* border: 3px solid #ec0; */
border: 3px solid #f23160;
border-radius: 50%;
height: 54px;
transition: border-radius 0.25s;
width: 54px;
}
.channel-sidebar-icon-image:hover {
border-radius: 30%;
transition: border-radius 0.25s;
}
.channel-sidebar-icon-initial {
align-items: center;
background-color: #3377cc;
/* border: 3px solid #85c742; */
/* border: 3px solid #ec0; */
border: 3px solid #f23160;
border-radius: 50%;
color: #eee;
display: flex;
font-family: sans-serif;
font-size: 34px;
font-weight: bold;
height: 54px;
justify-content: center;
transition: border-radius 0.25s;
width: 54px;
}
.channel-sidebar-icon-initial:hover {
border-radius: 30%;
transition: border-radius 0.25s;
}
.modal-add-account-channel {
align-items: center;
display: flex;
flex-direction: column;
height: 100%;
justify-content: space-between;
width: 100%;
}
.modal-add-account-channel-header {
align-items: center;
display: flex;
flex-direction: column;
}
.modal-add-account-channel-subtitle {
color: white;
font-family: sans-serif;
font-size: 14px;
margin-top: 10px;
text-align: center;
}
.modal-add-account-channel-title {
color: white;
font-family: sans-serif;
font-size: 24px;
font-weight: bold;
text-align: center;
}
.modal-add-account-channel-body {
align-items: center;
display: flex;
flex-direction: column;
width: 100%;
}
.modal-add-account-channel-button {
align-items: center;
background-color: #1f2e3c;
border: 1px solid #d6e0ea;
border-radius: 5px;
display: flex;
flex-direction: row;
justify-content: space-between;
margin: 5px 0px;
padding: 20px;
width: 100%;
}
.modal-add-account-channel-button:hover {
background-color: rgba(255, 255, 255, 0.1);
cursor: pointer;
}
.modal-add-account-channel-button-left {
color: white;
font-family: sans-serif;
font-size: 16px;
font-weight: bold;
}
.modal-add-account-channel-button-right-icon {
height: 20px;
width: 20px;
}
.modal-add-account-channel-input {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: 10px;
width: 100%;
}
.modal-add-account-channel-input-password {
background-color: #061726;
border: none;
border-radius: 5px 0px 0px 5px;
box-sizing: border-box;
color: white;
font-family: monospace;
font-size: 16px;
outline: none;
padding: 10px;
resize: none;
width: 90%;
}
.modal-add-account-channel-input-text {
background-color: #061726;
border: none;
border-radius: 5px;
box-sizing: border-box;
color: white;
font-family: monospace;
font-size: 16px;
outline: none;
padding: 10px;
resize: none;
width: 100%;
}
.modal-add-account-channel-input-show {
align-items: center;
background-color: #061726;
border: none;
border-radius: 0px 5px 5px 0px;
display: flex;
justify-content: center;
width: 10%;
}
.modal-add-account-channel-input-show:hover {
cursor: pointer;
}
.modal-add-account-channel-input-show-icon {
height: 16px;
width: 16px;
}
.modal-add-account-channel-label {
color: white;
font-family: sans-serif;
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
width: 100%;
}
.modal-add-account-channel-label-warning {
color: #f23160;
font-family: sans-serif;
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
width: 100%;
}
.modal-add-channel-description {
color: white;
font-family: sans-serif;
font-size: 16px;
font-weight: bold;
margin-top: 20px;
text-align: left;
width: 100%;
}
.modal-add-channel-description-subtext {
color: white;
font-family: sans-serif;
font-size: 16px;
margin-top: 10px;
text-align: left;
width: 100%;
}
.modal-add-channel-key {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
}
.modal-add-channel-key-input {
background-color: #061726;
border: none;
border-radius: 5px 0px 0px 5px;
box-sizing: border-box;
color: white;
font-family: monospace;
font-size: 16px;
outline: none;
padding: 10px;
resize: none;
width: 90%;
}
.modal-add-channel-key-show {
align-items: center;
background-color: #061726;
border: none;
border-radius: 0px 5px 5px 0px;
display: flex;
justify-content: center;
width: 10%;
}
.modal-add-channel-key-show:hover {
cursor: pointer;
}
.modal-add-channel-key-show-icon {
height: 16px;
width: 16px;
}
.modal-add-channel-label {
color: white;
font-family: sans-serif;
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
width: 100%;
}
.modal-add-channel-label-warning {
color: #f23160;
font-family: sans-serif;
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
width: 100%;
}
/* HTML: <div class="loader"></div> */
.loader {
width: 60px;
aspect-ratio: 6;
--_g: no-repeat radial-gradient(circle closest-side,#061726 90%,#0000);
background:
var(--_g) 0% 50%,
var(--_g) 50% 50%,
var(--_g) 100% 50%;
background-size: calc(100%/3) 100%;
animation: l7 1s infinite linear;
}
@keyframes l7 {
33%{background-size:calc(100%/3) 0% ,calc(100%/3) 100%,calc(100%/3) 100%}
50%{background-size:calc(100%/3) 100%,calc(100%/3) 0% ,calc(100%/3) 100%}
66%{background-size:calc(100%/3) 100%,calc(100%/3) 100%,calc(100%/3) 0% }
}

View file

@ -0,0 +1,503 @@
import { useEffect, useState } from 'react';
import { Modal, SmallModal } from './Modal';
import { AccountList, AddChannel, Login } from '../../wailsjs/go/main/App';
import { ChevronRight, CircleGreenBackground, Eye, EyeSlash, PlusCircle } from '../assets';
import './ChannelSideBar.css';
function ChannelSideBar(props) {
const [accounts, setAccounts] = useState({});
const [error, setError] = useState('');
const [addOpen, setAddOpen] = useState(false);
const [refresh, setRefresh] = useState(false);
const [scrollY, setScrollY] = useState(0);
useEffect(() => {
AccountList()
.then((response) => {
setAccounts(response);
})
.catch((error) => {
setError(error);
});
}, [refresh]);
const sortAccounts = () => {
let keys = Object.keys(accounts);
let sorted = [...keys].sort((a, b) =>
accounts[a].account.username.toLowerCase() > accounts[b].account.username.toLowerCase()
? 1
: -1
);
return sorted;
};
const handleScroll = (event) => {
setScrollY(event.target.scrollTop);
};
return (
<>
<ModalAdd
onClose={() => setAddOpen(false)}
onRefresh={() => {
setRefresh(!refresh);
}}
show={addOpen}
/>
<div className='channel-sidebar'>
<div className='channel-sidebar-body' onScroll={handleScroll}>
{sortAccounts().map((account, index) => (
<AccountChannels
account={accounts[account]}
key={index}
scrollY={scrollY}
top={index === 0}
/>
))}
</div>
<div className='channel-sidebar-footer'>
<ButtonIcon
hoverText={'Add an account/channel'}
onClick={() => setAddOpen(true)}
scrollY={0}
/>
</div>
</div>
</>
);
}
export default ChannelSideBar;
function AccountChannels(props) {
const sortChannels = () => {
let sorted = [...props.account.channels].sort((a, b) =>
a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1
);
return sorted;
};
if (props.account.account !== undefined) {
return (
<div
className='channel-sidebar-account-list'
style={props.top ? { borderTop: 'none' } : {}}
>
<AccountIcon account={props.account.account} key={0} scrollY={props.scrollY} />
{sortChannels().map((channel, index) => (
<ChannelIcon channel={channel} key={index + 1} scrollY={props.scrollY} />
))}
</div>
);
}
}
function AccountIcon(props) {
const [hover, setHover] = useState(false);
return (
<div
className='channel-sidebar-icon'
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
{props.account.profile_image === null ? (
<span className='channel-sidebar-icon-initial'>
{props.account.username[0].toUpperCase()}
</span>
) : (
<img className='channel-sidebar-icon-image' src={props.account.profile_image} />
)}
<img className='channel-sidebar-icon-account' src={CircleGreenBackground} />
{hover && (
<HoverName name={'/user/' + props.account.username} scrollY={props.scrollY} />
)}
</div>
);
}
function ButtonIcon(props) {
const [hover, setHover] = useState(false);
return (
<div
className='channel-sidebar-icon'
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
<button className='channel-sidebar-button' onClick={props.onClick}>
<img className='channel-sidebar-button-icon' src={PlusCircle} />
</button>
{hover && <HoverName name={props.hoverText} scrollY={props.scrollY} />}
</div>
);
}
function ChannelIcon(props) {
const [hover, setHover] = useState(false);
return (
<div
className='channel-sidebar-icon'
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
{props.channel.profile_image === null ? (
<span className='channel-sidebar-icon-initial'>
{props.channel.name[0].toUpperCase()}
</span>
) : (
<img className='channel-sidebar-icon-image' src={props.channel.profile_image} />
)}
{hover && (
<HoverName
name={'/c/' + props.channel.name.replace(/\s/g, '')}
scrollY={props.scrollY}
/>
)}
</div>
);
}
function HoverName(props) {
return (
<div
className='channel-sidebar-icon-hover'
style={{ transform: 'translate(75px, -' + (50 + props.scrollY) + 'px)' }}
>
<span className='channel-sidebar-icon-hover-text'>{props.name}</span>
</div>
);
}
function ModalAdd(props) {
const [accountPassword, setAccountPassword] = useState('');
const [accountPasswordValid, setAccountPasswordValid] = useState(true);
const updateAccountPassword = (event) => {
if (loading()) {
return;
}
setAccountPassword(event.target.value);
};
const [accountUsername, setAccountUsername] = useState('');
const [accountUsernameValid, setAccountUsernameValid] = useState(true);
const updateAccountUsername = (event) => {
if (loading()) {
return;
}
setAccountUsername(event.target.value);
};
const [addAccountLoading, setAddAccountLoading] = useState(false);
const [addChannelLoading, setAddChannelLoading] = useState(false);
const [channelKey, setChannelKey] = useState('');
const [channelKeyValid, setChannelKeyValid] = useState(true);
const updateChannelKey = (event) => {
if (loading()) {
return;
}
setChannelKey(event.target.value);
};
const [error, setError] = useState('');
const [stage, setStage] = useState('start');
useEffect(() => {
if (addAccountLoading) {
Login(accountUsername, accountPassword)
.then(() => {
reset();
props.onClose();
props.onRefresh();
})
.catch((error) => {
setAddAccountLoading(false);
setError(error);
});
}
}, [addAccountLoading]);
useEffect(() => {
if (addChannelLoading) {
AddChannel(channelKey)
.then(() => {
reset();
props.onClose();
props.onRefresh();
})
.catch((error) => {
setAddChannelLoading(false);
setError(error);
});
}
}, [addChannelLoading]);
const back = () => {
if (loading()) {
return;
}
reset();
};
const close = () => {
if (loading()) {
return;
}
reset();
props.onClose();
};
const reset = () => {
setStage('start');
resetAccount();
resetChannel();
};
const add = () => {
switch (stage) {
case 'account':
addAccount();
break;
case 'channel':
addChannel();
break;
default:
close();
}
};
const addAccount = () => {
if (loading()) {
return;
}
if (accountUsername === '') {
setAccountUsernameValid(false);
return;
}
if (accountPassword === '') {
setAccountPasswordValid(false);
return;
}
setAddAccountLoading(true);
};
const addChannel = () => {
if (loading()) {
return;
}
if (channelKey === '') {
setChannelKeyValid(false);
return;
}
setAddChannelLoading(true);
};
const loading = () => {
return addAccountLoading || addChannelLoading;
};
const resetAccount = () => {
setAccountPassword('');
setAccountPasswordValid(true);
setAccountUsername('');
setAccountUsernameValid(true);
setAddAccountLoading(false);
};
const resetChannel = () => {
setChannelKey('');
setChannelKeyValid(true);
setAddChannelLoading(false);
};
return (
<>
<SmallModal
onClose={() => setError('')}
show={error !== ''}
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
title={'Error'}
message={error}
submitButton={'OK'}
onSubmit={() => setError('')}
/>
<Modal
cancelButton={stage !== 'start' ? 'Back' : ''}
onCancel={back}
onClose={close}
show={props.show}
style={{ height: '480px', minHeight: '480px', width: '360px', minWidth: '360px' }}
submitButton={stage !== 'start' ? 'Add' : ''}
submitLoading={loading()}
onSubmit={add}
>
{stage === 'start' && <ModalAddStart setStage={setStage} />}
{stage === 'account' && (
<ModalAddAccount
accountPassword={accountPassword}
accountPasswordValid={accountPasswordValid}
updateAccountPassword={updateAccountPassword}
accountUsername={accountUsername}
accountUsernameValid={accountUsernameValid}
updateAccountUsername={updateAccountUsername}
/>
)}
{stage === 'channel' && (
<ModalAddChannel
channelKey={channelKey}
channelKeyValid={channelKeyValid}
updateChannelKey={updateChannelKey}
/>
)}
</Modal>
</>
);
}
function ModalAddAccount(props) {
const [showKey, setShowKey] = useState(false);
const updateShowKey = () => setShowKey(!showKey);
const [showPassword, setShowPassword] = useState(false);
const updateShowPassword = () => setShowPassword(!showPassword);
return (
<div className='modal-add-account-channel'>
<div className='modal-add-account-channel-header'>
<span className='modal-add-account-channel-title'>Add Account</span>
<span className='modal-add-account-channel-subtitle'>
Log into your Rumble account
</span>
</div>
<div className='modal-add-account-channel-body'>
{props.accountUsernameValid === false ? (
<label className='modal-add-account-channel-label-warning'>
USERNAME - Please enter a valid username
</label>
) : (
<label className='modal-add-account-channel-label'>USERNAME</label>
)}
<div className='modal-add-account-channel-input'>
<input
className='modal-add-account-channel-input-text'
onChange={!props.loading && props.updateAccountUsername}
placeholder={'Username'}
type={'text'}
value={props.accountUsername}
></input>
</div>
{props.accountPasswordValid === false ? (
<label className='modal-add-account-channel-label-warning'>
PASSWORD - Please enter a valid password
</label>
) : (
<label className='modal-add-account-channel-label'>PASSWORD</label>
)}
<div className='modal-add-account-channel-input'>
<input
className='modal-add-account-channel-input-password'
onChange={!props.loading && props.updateAccountPassword}
placeholder={'Password'}
type={showPassword ? 'text' : 'password'}
value={props.accountPassword}
></input>
<button
className='modal-add-account-channel-input-show'
onClick={updateShowPassword}
>
<img
className='modal-add-account-channel-input-show-icon'
src={showPassword ? EyeSlash : Eye}
/>
</button>
</div>
</div>
<div></div>
</div>
);
}
function ModalAddChannel(props) {
const [showKey, setShowKey] = useState(false);
const updateShowKey = () => setShowKey(!showKey);
return (
<div className='modal-add-account-channel'>
<div className='modal-add-account-channel-header'>
<span className='modal-add-account-channel-title'>Add Channel</span>
<span className='modal-add-account-channel-subtitle'>
Copy an API key below to add a channel
</span>
</div>
<div className='modal-add-account-channel-body'>
{props.channelKeyValid === false ? (
<label className='modal-add-channel-label-warning'>
API KEY - Please enter a valid API key
</label>
) : (
<label className='modal-add-channel-label'>API KEY</label>
)}
<div className='modal-add-channel-key'>
<input
className='modal-add-channel-key-input'
onChange={!props.loading && props.updateChannelKey}
placeholder={'Enter API key'}
type={showKey ? 'text' : 'password'}
value={props.channelKey}
></input>
<button className='modal-add-channel-key-show' onClick={updateShowKey}>
<img
className='modal-add-channel-key-show-icon'
src={showKey ? EyeSlash : Eye}
/>
</button>
</div>
<span className='modal-add-channel-description'>API KEYS SHOULD LOOK LIKE</span>
<span className='modal-add-channel-description-subtext'>
https://rumble.com/-livestream-api/get-data?key=really-long_string-of_random-characters
</span>
</div>
<div></div>
</div>
);
}
function ModalAddStart(props) {
return (
<div className='modal-add-account-channel'>
<span className='modal-add-account-channel-title'>Add an Account or Channel</span>
<div className='modal-add-account-channel-body'>
<button
className='modal-add-account-channel-button'
onClick={() => props.setStage('account')}
>
<div className='modal-add-account-channel-button-left'>
<span>Add Account</span>
</div>
<img
className='modal-add-account-channel-button-right-icon'
src={ChevronRight}
/>
</button>
<button
className='modal-add-account-channel-button'
onClick={() => props.setStage('channel')}
>
<div className='modal-add-account-channel-button-left'>
<span>Add Channel</span>
</div>
<img
className='modal-add-account-channel-button-right-icon'
src={ChevronRight}
/>
</button>
</div>
<div></div>
</div>
);
}

View file

@ -1,7 +1,8 @@
.modal-background { .modal-background {
align-items: center; align-items: center;
background-color: transparent; /* background-color: transparent; */
background-color: rgba(0,0,0,0.8);
display: flex; display: flex;
height: 100vh; height: 100vh;
justify-content: center; justify-content: center;
@ -29,6 +30,7 @@
font-weight: bold; font-weight: bold;
text-decoration: none; text-decoration: none;
/* width: 20%; */ /* width: 20%; */
height: 40px;
width: 70px; width: 70px;
} }
@ -36,12 +38,14 @@
background-color: transparent; background-color: transparent;
border: 1px solid #495a6a; border: 1px solid #495a6a;
border-radius: 5px; border-radius: 5px;
color: #495a6a; /* color: #495a6a; */
color: white;
cursor: pointer; cursor: pointer;
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
text-decoration: none; text-decoration: none;
/* width: 20%; */ /* width: 20%; */
height: 40px;
width: 70px; width: 70px;
} }
@ -55,6 +59,7 @@
font-weight: bold; font-weight: bold;
text-decoration: none; text-decoration: none;
/* width: 20%; */ /* width: 20%; */
height: 40px;
width: 70px; width: 70px;
} }
@ -79,8 +84,8 @@
.modal-container { .modal-container {
align-items: center; align-items: center;
background-color: rgba(6,23,38,1); background-color: #1f2e3c;
border: 1px solid #495a6a; /* border: 1px solid #495a6a; */
border-radius: 15px; border-radius: 15px;
color: black; color: black;
display: flex; display: flex;
@ -133,7 +138,7 @@
align-items: center; align-items: center;
/* background-color: rgba(6,23,38,1); */ /* background-color: rgba(6,23,38,1); */
background-color: white; background-color: white;
border: 1px solid #495a6a; /* border: 1px solid #495a6a; */
/* border: 1px solid black; */ /* border: 1px solid black; */
border-radius: 15px; border-radius: 15px;
color: black; color: black;

View file

@ -6,7 +6,7 @@ export function Modal(props) {
<div <div
className='modal-background' className='modal-background'
onClick={props.onClose} onClick={props.onClose}
style={{ zIndex: props.show ? 10 : -10 }} style={{ zIndex: props.show ? 8 : -8 }}
> >
<div <div
className='modal-container' className='modal-container'
@ -33,7 +33,12 @@ export function Modal(props) {
)} )}
{props.submitButton && ( {props.submitButton && (
<button className='modal-button' onClick={props.onSubmit}> <button className='modal-button' onClick={props.onSubmit}>
{props.submitButton} {/* {props.submitButton} */}
{props.submitLoading ? (
<div className='loader'></div>
) : (
props.submitButton
)}
</button> </button>
)} )}
</div> </div>

View file

@ -0,0 +1,7 @@
.dashboard {
align-items: center;
display: flex;
flex-direction: row;
height: 100vh;
width: 100%;
}

View file

@ -0,0 +1,14 @@
import { CircleGreenBackground, Heart } from '../assets';
import ChannelSideBar from '../components/ChannelSideBar';
import './Dashboard.css';
function Dashboard() {
return (
<div className='dashboard'>
<ChannelSideBar />
<div style={{ backgroundColor: '#1f2e3c', width: '100%', height: '100%' }}></div>
</div>
);
}
export default Dashboard;

View file

@ -1,11 +1,14 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { SmallModal } from '../components/Modal'; import { SmallModal } from '../components/Modal';
import { Login } from '../../wailsjs/go/main/App'; import { Login, SignedIn } from '../../wailsjs/go/main/App';
import { Eye, EyeSlash, Logo } from '../assets'; import { Eye, EyeSlash, Logo } from '../assets';
import { Navigate, useNavigate } from 'react-router-dom';
import './SignIn.css'; import './SignIn.css';
import { NavDashboard } from '../Navigation';
function SignIn() { function SignIn() {
const [error, setError] = useState(''); const [error, setError] = useState('');
const navigate = useNavigate();
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const updatePassword = (event) => setPassword(event.target.value); const updatePassword = (event) => setPassword(event.target.value);
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
@ -14,12 +17,25 @@ function SignIn() {
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
const updateUsername = (event) => setUsername(event.target.value); const updateUsername = (event) => setUsername(event.target.value);
useEffect(() => {
SignedIn()
.then((signedIn) => {
if (signedIn) {
navigate(NavDashboard);
}
})
.catch((error) => {
setError(error);
});
}, []);
useEffect(() => { useEffect(() => {
if (signingIn) { if (signingIn) {
Login(username, password) Login(username, password)
.then(() => { .then(() => {
setUsername(''); setUsername('');
setPassword(''); setPassword('');
navigate(NavDashboard);
}) })
.catch((error) => { .catch((error) => {
setError(error); setError(error);

View file

View file

View file

@ -11,6 +11,8 @@ const (
configDirNix = ".rum-goggles" configDirNix = ".rum-goggles"
configDirWin = "RumGoggles" configDirWin = "RumGoggles"
imageDir = "images"
logFile = "rumgoggles.log" logFile = "rumgoggles.log"
sqlFile = "rumgoggles.db" sqlFile = "rumgoggles.db"
) )
@ -32,6 +34,22 @@ func Database() (string, error) {
return path, nil return path, nil
} }
func ImageDir() (string, error) {
cfgDir, err := configDir()
if err != nil {
return "", pkgErr("error getting config directory", err)
}
dir := filepath.Join(cfgDir, imageDir)
err = os.MkdirAll(dir, 0750)
if err != nil {
return "", fmt.Errorf("error making directory: %v", err)
}
return dir, nil
}
// TODO: implement log rotation // TODO: implement log rotation
// Rotate log file every week? // Rotate log file every week?
// Keep most recent 4 logs? // Keep most recent 4 logs?

View file

@ -6,36 +6,59 @@ import (
) )
const ( const (
accountColumns = "id, username, cookies" accountColumns = "id, uid, username, cookies, profile_image, api_key"
accountTable = "account" accountTable = "account"
) )
type Account struct { type Account struct {
ID *int64 ID *int64 `json:"id"`
Username *string UID *string `json:"uid"`
Cookies *string Username *string `json:"username"`
Cookies *string `json:"cookies"`
ProfileImage *string `json:"profile_image"`
ApiKey *string `json:"api_key"`
}
func (a *Account) values() []any {
return []any{a.ID, a.UID, a.Username, a.Cookies, a.ProfileImage, a.ApiKey}
}
func (a *Account) valuesNoID() []any {
return a.values()[1:]
}
func (a *Account) valuesEndID() []any {
vals := a.values()
return append(vals[1:], vals[0])
} }
type sqlAccount struct { type sqlAccount struct {
id sql.NullInt64 id sql.NullInt64
username sql.NullString uid sql.NullString
cookies sql.NullString username sql.NullString
cookies sql.NullString
profileImage sql.NullString
apiKey sql.NullString
} }
func (sa *sqlAccount) scan(r Row) error { func (sa *sqlAccount) scan(r Row) error {
return r.Scan(&sa.id, &sa.username, &sa.cookies) return r.Scan(&sa.id, &sa.uid, &sa.username, &sa.cookies, &sa.profileImage, &sa.apiKey)
} }
func (sa sqlAccount) toAccount() *Account { func (sa sqlAccount) toAccount() *Account {
var a Account var a Account
a.ID = toInt64(sa.id) a.ID = toInt64(sa.id)
a.UID = toString(sa.uid)
a.Username = toString(sa.username) a.Username = toString(sa.username)
a.Cookies = toString(sa.cookies) a.Cookies = toString(sa.cookies)
a.ProfileImage = toString(sa.profileImage)
a.ApiKey = toString(sa.apiKey)
return &a return &a
} }
type AccountService interface { type AccountService interface {
All() ([]Account, error)
AutoMigrate() error AutoMigrate() error
ByUsername(username string) (*Account, error) ByUsername(username string) (*Account, error)
Create(a *Account) error Create(a *Account) error
@ -55,10 +78,41 @@ type accountService struct {
Database *sql.DB Database *sql.DB
} }
func (as *accountService) All() ([]Account, error) {
selectQ := fmt.Sprintf(`
SELECT %s
FROM "%s"
`, accountColumns, accountTable)
rows, err := as.Database.Query(selectQ)
if err != nil {
return nil, pkgErr("error executing select query", err)
}
defer rows.Close()
accounts := []Account{}
for rows.Next() {
sa := &sqlAccount{}
err = sa.scan(rows)
if err != nil {
return nil, pkgErr("error scanning row", err)
}
accounts = append(accounts, *sa.toAccount())
}
err = rows.Err()
if err != nil && err != sql.ErrNoRows {
return nil, pkgErr("error iterating over rows", err)
}
return accounts, nil
}
func (as *accountService) AutoMigrate() error { func (as *accountService) AutoMigrate() error {
err := as.createAccountTable() err := as.createAccountTable()
if err != nil { if err != nil {
return err return pkgErr(fmt.Sprintf("error creating %s table", accountTable), err)
} }
return nil return nil
@ -68,14 +122,17 @@ func (as *accountService) createAccountTable() error {
createQ := fmt.Sprintf(` createQ := fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS "%s" ( CREATE TABLE IF NOT EXISTS "%s" (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
uid TEXT UNIQUE,
username TEXT UNIQUE NOT NULL, username TEXT UNIQUE NOT NULL,
cookies TEXT cookies TEXT,
profile_image TEXT,
api_key TEXT
) )
`, accountTable) `, accountTable)
_, err := as.Database.Exec(createQ) _, err := as.Database.Exec(createQ)
if err != nil { if err != nil {
return fmt.Errorf("error creating table: %v", err) return fmt.Errorf("error executing create query: %v", err)
} }
return nil return nil
@ -103,7 +160,7 @@ func (as *accountService) ByUsername(username string) (*Account, error) {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return nil, nil return nil, nil
} }
return nil, pkgErr(fmt.Sprintf("error querying \"%s\" by username", accountTable), err) return nil, pkgErr("error executing select query", err)
} }
return sa.toAccount(), nil return sa.toAccount(), nil
@ -124,22 +181,31 @@ func (as *accountService) Create(a *Account) error {
VALUES (%s) VALUES (%s)
`, accountTable, columns, values(columns)) `, accountTable, columns, values(columns))
_, err = as.Database.Exec(insertQ, a.Username, a.Cookies) _, err = as.Database.Exec(insertQ, a.valuesNoID()...)
if err != nil { if err != nil {
return pkgErr(fmt.Sprintf("error inserting %s", accountTable), err) return pkgErr("error executing insert query", err)
} }
return nil return nil
} }
func (as *accountService) DestructiveReset() error { func (as *accountService) DestructiveReset() error {
err := as.dropAccountTable()
if err != nil {
return pkgErr(fmt.Sprintf("error dropping %s table", accountTable), err)
}
return nil
}
func (as *accountService) dropAccountTable() error {
dropQ := fmt.Sprintf(` dropQ := fmt.Sprintf(`
DROP TABLE IF EXISTS "%s" DROP TABLE IF EXISTS "%s"
`, accountTable) `, accountTable)
_, err := as.Database.Exec(dropQ) _, err := as.Database.Exec(dropQ)
if err != nil { if err != nil {
return fmt.Errorf("error dropping table: %v", err) return fmt.Errorf("error executing drop query: %v", err)
} }
return nil return nil
@ -162,9 +228,9 @@ func (as *accountService) Update(a *Account) error {
WHERE id=? WHERE id=?
`, accountTable, set(columns)) `, accountTable, set(columns))
_, err = as.Database.Exec(updateQ, a.Username, a.Cookies, a.ID) _, err = as.Database.Exec(updateQ, a.valuesEndID()...)
if err != nil { if err != nil {
return pkgErr(fmt.Sprintf("error updating %s", accountTable), err) return pkgErr(fmt.Sprintf("error executing update query", accountTable), err)
} }
return nil return nil

View file

@ -0,0 +1,94 @@
package models
import (
"database/sql"
"fmt"
)
const (
accountChannelColumns = "a.id, a.uid, a.username, a.cookies, a.profile_image, a.api_key, c.id, c.account_id, c.cid, c.name, c.profile_image, c.api_key"
)
type AccountChannel struct {
Account
Channel
}
type sqlAccountChannel struct {
sqlAccount
sqlChannel
}
func (sac *sqlAccountChannel) scan(r Row) error {
return r.Scan(
&sac.sqlAccount.id,
&sac.sqlAccount.uid,
&sac.sqlAccount.username,
&sac.sqlAccount.cookies,
&sac.sqlAccount.profileImage,
&sac.sqlAccount.apiKey,
&sac.sqlChannel.id,
&sac.sqlChannel.accountID,
&sac.sqlChannel.cid,
&sac.sqlChannel.name,
&sac.sqlChannel.profileImage,
&sac.sqlChannel.apiKey,
)
}
func (sac *sqlAccountChannel) toAccountChannel() *AccountChannel {
var ac AccountChannel
ac.Account = *sac.toAccount()
ac.Channel = *sac.toChannel()
return &ac
}
type AccountChannelService interface {
All() ([]AccountChannel, error)
}
func NewAccountChannelService(db *sql.DB) AccountChannelService {
return &accountChannelService{
Database: db,
}
}
var _ AccountChannelService = &accountChannelService{}
type accountChannelService struct {
Database *sql.DB
}
func (as *accountChannelService) All() ([]AccountChannel, error) {
selectQ := fmt.Sprintf(`
SELECT %s
FROM "%s" a
LEFT JOIN "%s" c ON a.id=c.account_id
`, accountChannelColumns, accountTable, channelTable)
rows, err := as.Database.Query(selectQ)
if err != nil {
return nil, pkgErr("error executing select query", err)
}
defer rows.Close()
accountChannels := []AccountChannel{}
for rows.Next() {
sac := &sqlAccountChannel{}
err = sac.scan(rows)
if err != nil {
return nil, pkgErr("error scanning row", err)
}
accountChannels = append(accountChannels, *sac.toAccountChannel())
}
err = rows.Err()
if err != nil && err != sql.ErrNoRows {
return nil, pkgErr("error iterating over rows", err)
}
return accountChannels, nil
}

View file

@ -0,0 +1,232 @@
package models
import (
"database/sql"
"fmt"
)
const (
channelColumns = "id, account_id, cid, name, profile_image, api_key"
channelTable = "channel"
)
type Channel struct {
ID *int64 `json:"id"`
AccountID *int64 `json:"account_id"`
CID *string `json:"cid"`
Name *string `json:"name"`
ProfileImage *string `json:"profile_image"`
ApiKey *string `json:"api_key"`
}
func (c *Channel) values() []any {
return []any{c.ID, c.AccountID, c.CID, c.Name, c.ProfileImage, c.ApiKey}
}
func (c *Channel) valuesNoID() []any {
return c.values()[1:]
}
func (c *Channel) valuesEndID() []any {
vals := c.values()
return append(vals[1:], vals[0])
}
type sqlChannel struct {
id sql.NullInt64
accountID sql.NullInt64
cid sql.NullString
name sql.NullString
profileImage sql.NullString
apiKey sql.NullString
}
func (sc *sqlChannel) scan(r Row) error {
return r.Scan(&sc.id, &sc.accountID, &sc.cid, &sc.name, &sc.profileImage, &sc.apiKey)
}
func (sc sqlChannel) toChannel() *Channel {
var c Channel
c.ID = toInt64(sc.id)
c.AccountID = toInt64(sc.accountID)
c.CID = toString(sc.cid)
c.Name = toString(sc.name)
c.ProfileImage = toString(sc.profileImage)
c.ApiKey = toString(sc.apiKey)
return &c
}
type ChannelService interface {
AutoMigrate() error
ByName(name string) (*Channel, error)
Create(c *Channel) error
DestructiveReset() error
}
func NewChannelService(db *sql.DB) ChannelService {
return &channelService{
Database: db,
}
}
var _ ChannelService = &channelService{}
type channelService struct {
Database *sql.DB
}
func (cs *channelService) AutoMigrate() error {
err := cs.createChannelTable()
if err != nil {
return pkgErr(fmt.Sprintf("error creating %s table", channelTable), err)
}
return nil
}
func (cs *channelService) createChannelTable() error {
createQ := fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS "%s" (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL,
cid TEXT UNIQUE NOT NULL,
name TEXT UNIQUE NOT NULL,
profile_image TEXT,
api_key TEXT NOT NULL,
FOREIGN KEY (account_id) REFERENCES "%s" (id)
)
`, channelTable, accountTable)
_, err := cs.Database.Exec(createQ)
if err != nil {
return fmt.Errorf("error executing create query: %v", err)
}
return nil
}
func (cs *channelService) ByName(name string) (*Channel, error) {
err := runChannelValFuncs(
&Channel{Name: &name},
channelRequireName,
)
if err != nil {
return nil, pkgErr("", err)
}
selectQ := fmt.Sprintf(`
SELECT %s
FROM "%s"
WHERE name=?
`, channelColumns, channelTable)
var sc sqlChannel
row := cs.Database.QueryRow(selectQ, name)
err = sc.scan(row)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, pkgErr("error executing select query", err)
}
return sc.toChannel(), nil
}
func (cs *channelService) Create(c *Channel) error {
err := runChannelValFuncs(
c,
channelRequireAccountID,
channelRequireApiKey,
channelRequireCID,
channelRequireName,
)
if err != nil {
return pkgErr("invalid channel", err)
}
columns := columnsNoID(channelColumns)
insertQ := fmt.Sprintf(`
INSERT INTO "%s" (%s)
VALUES (%s)
`, channelTable, columns, values(columns))
_, err = cs.Database.Exec(insertQ, c.valuesNoID()...)
if err != nil {
return pkgErr("error executing insert query", err)
}
return nil
}
func (cs *channelService) DestructiveReset() error {
err := cs.dropChannelTable()
if err != nil {
return pkgErr(fmt.Sprintf("error dropping %s table", channelTable), err)
}
return nil
}
func (cs *channelService) dropChannelTable() error {
dropQ := fmt.Sprintf(`
DROP TABLE IF EXISTS "%s"
`, channelTable)
_, err := cs.Database.Exec(dropQ)
if err != nil {
return fmt.Errorf("error executing drop query: %v", err)
}
return nil
}
type channelValFunc func(*Channel) error
func runChannelValFuncs(c *Channel, fns ...channelValFunc) error {
if c == nil {
return fmt.Errorf("channel cannot be nil")
}
for _, fn := range fns {
err := fn(c)
if err != nil {
return err
}
}
return nil
}
func channelRequireAccountID(c *Channel) error {
if c.AccountID == nil || *c.AccountID <= 0 {
return ErrChannelInvalidAccountID
}
return nil
}
func channelRequireApiKey(c *Channel) error {
if c.ApiKey == nil || *c.ApiKey == "" {
return ErrChannelInvalidApiKey
}
return nil
}
func channelRequireCID(c *Channel) error {
if c.CID == nil || *c.CID == "" {
return ErrChannelInvalidCID
}
return nil
}
func channelRequireName(c *Channel) error {
if c.Name == nil || *c.Name == "" {
return ErrChannelInvalidName
}
return nil
}

View file

@ -7,6 +7,11 @@ const (
ErrAccountInvalidUsername ValidatorError = "invalid account username" ErrAccountInvalidUsername ValidatorError = "invalid account username"
ErrAccountInvalidID ValidatorError = "invalid account id" ErrAccountInvalidID ValidatorError = "invalid account id"
ErrChannelInvalidAccountID ValidatorError = "invalid channel account id"
ErrChannelInvalidApiKey ValidatorError = "invalid channel API key"
ErrChannelInvalidCID ValidatorError = "invalid channel CID"
ErrChannelInvalidName ValidatorError = "invalid channel name"
) )
func pkgErr(prefix string, err error) error { func pkgErr(prefix string, err error) error {

View file

@ -7,23 +7,27 @@ import (
type migrationFunc func() error type migrationFunc func() error
type service struct { type table struct {
name string name string
automigrate migrationFunc automigrate migrationFunc
destructivereset migrationFunc destructivereset migrationFunc
} }
type Services struct { type Services struct {
AccountS AccountService AccountS AccountService
Database *sql.DB AccountChannelS AccountChannelService
services []service ChannelS ChannelService
Database *sql.DB
tables []table
} }
func (s *Services) AutoMigrate() error { func (s *Services) AutoMigrate() error {
for _, service := range s.services { for _, table := range s.tables {
err := service.automigrate() if table.automigrate != nil {
if err != nil { err := table.automigrate()
return pkgErr(fmt.Sprintf("error auto-migrating %s service", service.name), err) if err != nil {
return pkgErr(fmt.Sprintf("error auto-migrating %s table", table.name), err)
}
} }
} }
@ -40,10 +44,12 @@ func (s *Services) Close() error {
} }
func (s *Services) DestructiveReset() error { func (s *Services) DestructiveReset() error {
for _, service := range s.services { for _, table := range s.tables {
err := service.destructivereset() if table.destructivereset != nil {
if err != nil { err := table.destructivereset()
return pkgErr(fmt.Sprintf("error destructive-resetting %s service", service.name), err) if err != nil {
return pkgErr(fmt.Sprintf("error destructive-resetting %s table", table.name), err)
}
} }
} }
@ -78,7 +84,24 @@ func WithDatabase(file string) ServicesInit {
func WithAccountService() ServicesInit { func WithAccountService() ServicesInit {
return func(s *Services) error { return func(s *Services) error {
s.AccountS = NewAccountService(s.Database) s.AccountS = NewAccountService(s.Database)
s.services = append(s.services, service{accountTable, s.AccountS.AutoMigrate, s.AccountS.DestructiveReset}) s.tables = append(s.tables, table{accountTable, s.AccountS.AutoMigrate, s.AccountS.DestructiveReset})
return nil
}
}
func WithAccountChannelService() ServicesInit {
return func(s *Services) error {
s.AccountChannelS = NewAccountChannelService(s.Database)
return nil
}
}
func WithChannelService() ServicesInit {
return func(s *Services) error {
s.ChannelS = NewChannelService(s.Database)
s.tables = append(s.tables, table{channelTable, s.ChannelS.AutoMigrate, s.ChannelS.DestructiveReset})
return nil return nil
} }

View file

@ -2,7 +2,10 @@ package main
import ( import (
"embed" "embed"
"net/http"
"strings"
"github.com/tylertravisty/rum-goggles/v1/internal/config"
"github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/assetserver"
@ -21,7 +24,8 @@ func main() {
Width: 1024, Width: 1024,
Height: 768, Height: 768,
AssetServer: &assetserver.Options{ AssetServer: &assetserver.Options{
Assets: assets, Assets: assets,
Handler: http.HandlerFunc(GetImage),
}, },
BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255}, BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255},
OnShutdown: app.shutdown, OnShutdown: app.shutdown,
@ -35,3 +39,12 @@ func main() {
println("Error:", err.Error()) println("Error:", err.Error())
} }
} }
func GetImage(w http.ResponseWriter, r *http.Request) {
path := strings.Replace(r.RequestURI, "wails://wails", "", 1)
prefix, err := config.ImageDir()
if err != nil {
return
}
http.ServeFile(w, r, prefix+path)
}