project initiate.

This commit is contained in:
Vixalie
2025-02-26 05:39:36 +08:00
commit 4f5420b658
81 changed files with 4816 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
@layer pages {
.ble_control {
height: calc(var(--spacing) * 12);
padding-inline: calc(var(--spacing) * 2);
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
gap: calc(var(--spacing) * 2);
}
}

View File

@@ -0,0 +1,17 @@
import { useAtomValue } from 'jotai';
import { FC } from 'react';
import { BleState } from '../../context/EstimContext';
import styles from './BleControl.module.css';
const BleControl: FC = () => {
const bleState = useAtomValue(BleState);
return (
<div className={styles.ble_control}>
<button disabled={!bleState.ready || bleState.searching}>Scan</button>
<button disabled={!bleState.ready || !bleState.connected}>Disconnect</button>
</div>
);
};
export default BleControl;

View File

@@ -0,0 +1,8 @@
@layer pages {
.device_detail {
flex: 2;
border-radius: calc(var(--border-radius) * 2);
padding: calc(var(--spacing) * 2);
background-color: var(--color-dark-surface-container);
}
}

View File

@@ -0,0 +1,8 @@
import { FC } from 'react';
import styles from './DeviceDetail.module.css';
const DeviceDetail: FC = () => {
return <div className={styles.device_detail}></div>;
};
export default DeviceDetail;

View File

@@ -0,0 +1,8 @@
@layer pages {
.devices {
flex: 1;
display: flex;
flex-direction: column;
align-items: stretch;
}
}

View File

@@ -0,0 +1,13 @@
import { FC } from 'react';
import { ScrollArea } from '../../components/ScrollArea';
import styles from './DeviceList.module.css';
const DeviceList: FC = () => {
return (
<div className={styles.devices}>
<ScrollArea enableY></ScrollArea>
</div>
);
};
export default DeviceList;

View File

@@ -0,0 +1,11 @@
@layer pages {
.channel_host {
flex: 1;
padding: calc(var(--spacing) * 2) calc(var(--spacing) * 3);
border-radius: calc(var(--border-radius) * 2);
background-color: var(--color-dark-surface-container);
display: flex;
flex-direction: column;
gap: calc(var(--spacing));
}
}

View File

@@ -0,0 +1,17 @@
import { FC } from 'react';
import { Channels } from '../../context/EstimContext';
import styles from './ChannelHost.module.css';
type ChannelHostProps = {
channel: Channels;
};
const ChannelHost: FC<ChannelHostProps> = ({ channel }) => {
return (
<div className={styles.channel_host}>
<h3>Channel {channel.toUpperCase()}</h3>
</div>
);
};
export default ChannelHost;

View File

@@ -0,0 +1,30 @@
import { useAtomValue } from 'jotai';
import { FC, useMemo } from 'react';
import { BleState } from '../../context/EstimContext';
import IconBluetooth from '../../icons/IconBluetooth';
const BleStates: FC = () => {
const ble = useAtomValue(BleState);
const bleIcon = useMemo(() => {
if (ble.ready && !ble.searching && !ble.connected) {
return 'material-symbols-light:bluetooth';
} else if (ble.ready && ble.searching) {
return 'material-symbols-light:bluetooth-searching';
} else if (ble.ready && !ble.searching && ble.connected) {
return 'material-symbols-light:bluetooth-connected';
} else {
return 'material-symbols-light:bluetooth-disabled';
}
}, [ble]);
return (
<IconBluetooth
height={16}
ready={ble.ready}
searching={ble.searching}
connected={ble.connected?.length > 0}
/>
);
};
export default BleStates;

View File

@@ -0,0 +1,10 @@
@layer pages {
.channel_state {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: calc(var(--spacing));
font-size: calc(var(--font-size) * 1.4);
}
}

View File

@@ -0,0 +1,35 @@
import { Icon } from '@iconify/react/dist/iconify.js';
import { useAtomValue } from 'jotai';
import { FC } from 'react';
import { Channels, ChannelState } from '../../context/EstimContext';
import { smallIconProps } from '../../icons/shared-props';
import styles from './ChannelStates.module.css';
const ChannelStates: FC<{ channel: Channels }> = ({ channel }) => {
const chState = useAtomValue(ChannelState(channel));
return (
<div className={styles.channel_state}>
<span>Ch {channel.toUpperCase()}</span>
{chState.playing ? (
<Icon icon="material-symbols-light:electric-bolt" {...smallIconProps} />
) : (
<Icon icon="material-symbols-light:stop" {...smallIconProps} />
)}
<span>{chState.strength}</span>
<Icon icon="material-symbols-light:arrow-upload-progress" {...smallIconProps} />
<span>{chState.boostLevel}</span>
{chState.playMode === 'shuffle' && (
<Icon icon="material-symbols-light:shuffle" {...smallIconProps} />
)}
{chState.playMode === 'repeat' && (
<Icon icon="material-symbols-light:repeat" {...smallIconProps} />
)}
{chState.playMode === 'repeat-one' && (
<Icon icon="material-symbols-light:repeat-one" {...smallIconProps} />
)}
</div>
);
};
export default ChannelStates;

View File

@@ -0,0 +1,18 @@
import { useAtomValue } from 'jotai';
import { FC } from 'react';
import { DeviceState } from '../../context/EstimContext';
import IconBattery from '../../icons/IconBattery';
import IconRssi from '../../icons/IconRssi';
const DeviceStates: FC = () => {
const deviceState = useAtomValue(DeviceState);
return (
<>
<IconRssi height={16} level={deviceState.rssi} />
<IconBattery height={16} level={deviceState.battery} />
</>
);
};
export default DeviceStates;

View File

@@ -0,0 +1,13 @@
@layer pages {
.state_bar {
flex: 1;
padding-block-start: calc(var(--spacing));
padding-inline: calc(var(--spacing) * 2);
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
gap: calc(var(--spacing) * 4);
font-size: calc(var(--font-size) * 1.6);
}
}

View File

@@ -0,0 +1,18 @@
import { FC } from 'react';
import BleStates from './BleState';
import ChannelStates from './ChannelStates';
import DeviceStates from './DeviceStates';
import styles from './StateBar.module.css';
const StateBar: FC = () => {
return (
<div className={styles.state_bar}>
<BleStates />
<ChannelStates channel="a" />
<ChannelStates channel="b" />
<DeviceStates />
</div>
);
};
export default StateBar;