mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-05-07 13:55:12 +02:00
Dead and trivial code rip out (whoof)
This commit is contained in:
@@ -58,7 +58,6 @@ await RawPacketRepository.mark_decrypted(packet_id, message_id)
|
||||
settings = await AppSettingsRepository.get()
|
||||
await AppSettingsRepository.update(auto_decrypt_dm_on_advert=True)
|
||||
await AppSettingsRepository.add_favorite("contact", public_key)
|
||||
await AppSettingsRepository.update_last_message_time("channel-KEY", timestamp)
|
||||
```
|
||||
|
||||
### Radio Connection
|
||||
@@ -512,7 +511,6 @@ All endpoints are prefixed with `/api`.
|
||||
- `POST /api/settings/favorites` - Add a favorite
|
||||
- `DELETE /api/settings/favorites` - Remove a favorite
|
||||
- `POST /api/settings/favorites/toggle` - Toggle favorite status
|
||||
- `POST /api/settings/last-message-time` - Update last message time for a conversation
|
||||
- `POST /api/settings/migrate` - One-time migration from frontend localStorage
|
||||
|
||||
### WebSocket
|
||||
|
||||
@@ -874,33 +874,6 @@ class AppSettingsRepository:
|
||||
]
|
||||
return await AppSettingsRepository.update(favorites=new_favorites)
|
||||
|
||||
@staticmethod
|
||||
async def update_last_message_time(state_key: str, timestamp: int) -> None:
|
||||
"""Update the last message time for a conversation atomically.
|
||||
|
||||
Only updates if the new timestamp is greater than the existing one.
|
||||
Uses SQLite's json_set for atomic update to avoid race conditions.
|
||||
"""
|
||||
# Use COALESCE to handle NULL or missing keys, json_set for atomic update
|
||||
# Only update if new timestamp > existing (or key doesn't exist)
|
||||
await db.conn.execute(
|
||||
"""
|
||||
UPDATE app_settings
|
||||
SET last_message_times = json_set(
|
||||
COALESCE(last_message_times, '{}'),
|
||||
'$.' || ?,
|
||||
?
|
||||
)
|
||||
WHERE id = 1
|
||||
AND (
|
||||
json_extract(last_message_times, '$.' || ?) IS NULL
|
||||
OR json_extract(last_message_times, '$.' || ?) < ?
|
||||
)
|
||||
""",
|
||||
(state_key, timestamp, state_key, state_key, timestamp),
|
||||
)
|
||||
await db.conn.commit()
|
||||
|
||||
@staticmethod
|
||||
async def migrate_preferences_from_frontend(
|
||||
favorites: list[dict],
|
||||
|
||||
@@ -63,13 +63,6 @@ class FavoriteRequest(BaseModel):
|
||||
id: str = Field(description="Channel key or contact public key")
|
||||
|
||||
|
||||
class LastMessageTimeUpdate(BaseModel):
|
||||
state_key: str = Field(
|
||||
description="Conversation state key (e.g., 'channel-KEY' or 'contact-PREFIX')"
|
||||
)
|
||||
timestamp: int = Field(description="Unix timestamp of the last message")
|
||||
|
||||
|
||||
class MigratePreferencesRequest(BaseModel):
|
||||
favorites: list[FavoriteRequest] = Field(
|
||||
default_factory=list,
|
||||
@@ -144,17 +137,6 @@ async def toggle_favorite(request: FavoriteRequest) -> AppSettings:
|
||||
return await AppSettingsRepository.add_favorite(request.type, request.id)
|
||||
|
||||
|
||||
@router.post("/last-message-time")
|
||||
async def update_last_message_time(request: LastMessageTimeUpdate) -> dict:
|
||||
"""Update the last message time for a conversation.
|
||||
|
||||
Used to track when conversations last received messages for sidebar sorting.
|
||||
Only updates if the new timestamp is greater than the existing one.
|
||||
"""
|
||||
await AppSettingsRepository.update_last_message_time(request.state_key, request.timestamp)
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@router.post("/migrate", response_model=MigratePreferencesResponse)
|
||||
async def migrate_preferences(request: MigratePreferencesRequest) -> MigratePreferencesResponse:
|
||||
"""Migrate all preferences from frontend localStorage to database.
|
||||
|
||||
+4
-12
@@ -192,19 +192,11 @@ server: {
|
||||
|
||||
## Type Definitions (`types.ts`)
|
||||
|
||||
### Key Type Aliases
|
||||
|
||||
```typescript
|
||||
type PublicKey = string; // 64-char hex identifying a contact/node
|
||||
type PubkeyPrefix = string; // 12-char hex prefix (used in message routing)
|
||||
type ChannelKey = string; // 32-char hex identifying a channel
|
||||
```
|
||||
|
||||
### Key Interfaces
|
||||
|
||||
```typescript
|
||||
interface Contact {
|
||||
public_key: PublicKey;
|
||||
public_key: string; // 64-char hex public key
|
||||
name: string | null;
|
||||
type: number; // 0=unknown, 1=client, 2=repeater, 3=room
|
||||
on_radio: boolean;
|
||||
@@ -215,7 +207,7 @@ interface Contact {
|
||||
}
|
||||
|
||||
interface Channel {
|
||||
key: ChannelKey;
|
||||
key: string; // 32-char hex channel key
|
||||
name: string;
|
||||
is_hashtag: boolean;
|
||||
on_radio: boolean;
|
||||
@@ -224,7 +216,7 @@ interface Channel {
|
||||
interface Message {
|
||||
id: number;
|
||||
type: 'PRIV' | 'CHAN';
|
||||
conversation_key: string; // PublicKey for PRIV, ChannelKey for CHAN
|
||||
conversation_key: string; // public key for PRIV, channel key for CHAN
|
||||
text: string;
|
||||
outgoing: boolean;
|
||||
acked: number; // 0=not acked, 1+=ack count (flood echoes)
|
||||
@@ -233,7 +225,7 @@ interface Message {
|
||||
|
||||
interface Conversation {
|
||||
type: 'contact' | 'channel' | 'raw' | 'map' | 'visualizer';
|
||||
id: string; // PublicKey for contacts, ChannelKey for channels, 'raw'/'map'/'visualizer' for special views
|
||||
id: string; // public key for contacts, channel key for channels, 'raw'/'map'/'visualizer' for special views
|
||||
name: string;
|
||||
}
|
||||
|
||||
|
||||
-15
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-572
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -13,7 +13,7 @@
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<script type="module" crossorigin src="/assets/index-BrthzT77.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-CWnjp-zX.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DJA5wYVF.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -204,13 +204,6 @@ export const api = {
|
||||
body: JSON.stringify({ type, id }),
|
||||
}),
|
||||
|
||||
// Last message time tracking
|
||||
updateLastMessageTime: (stateKey: string, timestamp: number) =>
|
||||
fetchJson<{ status: string }>('/settings/last-message-time', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ state_key: stateKey, timestamp }),
|
||||
}),
|
||||
|
||||
// Preferences migration (one-time, from localStorage to database)
|
||||
migratePreferences: (request: MigratePreferencesRequest) =>
|
||||
fetchJson<MigratePreferencesResponse>('/settings/migrate', {
|
||||
|
||||
@@ -12,8 +12,7 @@ import {
|
||||
type SimulationLinkDatum,
|
||||
} from 'd3-force';
|
||||
import { MeshCoreDecoder, PayloadType } from '@michaelhart/meshcore-decoder';
|
||||
import type { Contact, RawPacket, RadioConfig } from '../types';
|
||||
import { CONTACT_TYPE_REPEATER } from '../utils/contactAvatar';
|
||||
import { CONTACT_TYPE_REPEATER, type Contact, type RawPacket, type RadioConfig } from '../types';
|
||||
import { Checkbox } from './ui/checkbox';
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { useState } from 'react';
|
||||
import type { Contact, Channel, Conversation, Favorite } from '../types';
|
||||
import {
|
||||
CONTACT_TYPE_REPEATER,
|
||||
type Contact,
|
||||
type Channel,
|
||||
type Conversation,
|
||||
type Favorite,
|
||||
} from '../types';
|
||||
import { getStateKey, type ConversationTimes } from '../utils/conversationState';
|
||||
import { getContactDisplayName } from '../utils/pubkey';
|
||||
import { ContactAvatar } from './ContactAvatar';
|
||||
import { CONTACT_TYPE_REPEATER } from '../utils/contactAvatar';
|
||||
import { isFavorite } from '../utils/favorites';
|
||||
import { Input } from './ui/input';
|
||||
import { Button } from './ui/button';
|
||||
@@ -32,11 +37,6 @@ interface SidebarProps {
|
||||
onSortOrderChange?: (order: SortOrder) => void;
|
||||
}
|
||||
|
||||
/** Format unread count for display */
|
||||
function formatUnreadCount(count: number): string {
|
||||
return `${count}`;
|
||||
}
|
||||
|
||||
export function Sidebar({
|
||||
contacts,
|
||||
channels,
|
||||
@@ -379,7 +379,7 @@ export function Sidebar({
|
||||
: 'bg-primary text-primary-foreground'
|
||||
)}
|
||||
>
|
||||
{formatUnreadCount(unreadCount)}
|
||||
{unreadCount}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -422,7 +422,7 @@ export function Sidebar({
|
||||
: 'bg-primary text-primary-foreground'
|
||||
)}
|
||||
>
|
||||
{formatUnreadCount(unreadCount)}
|
||||
{unreadCount}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -474,7 +474,7 @@ export function Sidebar({
|
||||
: 'bg-primary text-primary-foreground'
|
||||
)}
|
||||
>
|
||||
{formatUnreadCount(unreadCount)}
|
||||
{unreadCount}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -535,7 +535,7 @@ export function Sidebar({
|
||||
: 'bg-primary text-primary-foreground'
|
||||
)}
|
||||
>
|
||||
{formatUnreadCount(unreadCount)}
|
||||
{unreadCount}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
export { useRepeaterMode, type UseRepeaterModeResult } from './useRepeaterMode';
|
||||
export { useUnreadCounts, type UseUnreadCountsResult } from './useUnreadCounts';
|
||||
export {
|
||||
useConversationMessages,
|
||||
type UseConversationMessagesResult,
|
||||
getMessageContentKey,
|
||||
} from './useConversationMessages';
|
||||
export { useRepeaterMode } from './useRepeaterMode';
|
||||
export { useUnreadCounts } from './useUnreadCounts';
|
||||
export { useConversationMessages, getMessageContentKey } from './useConversationMessages';
|
||||
|
||||
@@ -15,7 +15,6 @@ export interface UseUnreadCountsResult {
|
||||
lastMessageTimes: ConversationTimes;
|
||||
incrementUnread: (stateKey: string, hasMention?: boolean) => void;
|
||||
markAllRead: () => void;
|
||||
markConversationRead: (conv: Conversation) => void;
|
||||
trackNewMessage: (msg: Message) => void;
|
||||
}
|
||||
|
||||
@@ -136,45 +135,6 @@ export function useUnreadCounts(
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Mark a specific conversation as read
|
||||
// Calls server API to persist read state across devices
|
||||
const markConversationRead = useCallback((conv: Conversation) => {
|
||||
if (conv.type === 'raw' || conv.type === 'map' || conv.type === 'visualizer') return;
|
||||
|
||||
const key = getStateKey(conv.type as 'channel' | 'contact', conv.id);
|
||||
|
||||
// Update local state immediately
|
||||
setUnreadCounts((prev) => {
|
||||
if (prev[key]) {
|
||||
const next = { ...prev };
|
||||
delete next[key];
|
||||
return next;
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
|
||||
// Also clear mentions for this conversation
|
||||
setMentions((prev) => {
|
||||
if (prev[key]) {
|
||||
const next = { ...prev };
|
||||
delete next[key];
|
||||
return next;
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
|
||||
// Persist to server (fire-and-forget)
|
||||
if (conv.type === 'channel') {
|
||||
api.markChannelRead(conv.id).catch((err) => {
|
||||
console.error('Failed to mark channel as read on server:', err);
|
||||
});
|
||||
} else if (conv.type === 'contact') {
|
||||
api.markContactRead(conv.id).catch((err) => {
|
||||
console.error('Failed to mark contact as read on server:', err);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Track a new incoming message for unread counts
|
||||
const trackNewMessage = useCallback((msg: Message) => {
|
||||
let conversationKey: string | null = null;
|
||||
@@ -197,7 +157,6 @@ export function useUnreadCounts(
|
||||
lastMessageTimes,
|
||||
incrementUnread,
|
||||
markAllRead,
|
||||
markConversationRead,
|
||||
trackNewMessage,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
getAvatarText,
|
||||
getAvatarColor,
|
||||
getContactAvatar,
|
||||
CONTACT_TYPE_REPEATER,
|
||||
} from '../utils/contactAvatar';
|
||||
import { getAvatarText, getAvatarColor, getContactAvatar } from '../utils/contactAvatar';
|
||||
import { CONTACT_TYPE_REPEATER } from '../types';
|
||||
|
||||
describe('getAvatarText', () => {
|
||||
it('returns first emoji when name contains emoji', () => {
|
||||
|
||||
+2
-13
@@ -1,14 +1,3 @@
|
||||
/**
|
||||
* Type aliases for key types used throughout the application.
|
||||
* These are all hex strings but serve different purposes.
|
||||
*/
|
||||
|
||||
/** 64-character hex string identifying a contact/node */
|
||||
export type PublicKey = string;
|
||||
|
||||
/** 32-character hex string identifying a channel */
|
||||
export type ChannelKey = string;
|
||||
|
||||
export interface RadioSettings {
|
||||
freq: number;
|
||||
bw: number;
|
||||
@@ -48,7 +37,7 @@ export interface MaintenanceResult {
|
||||
}
|
||||
|
||||
export interface Contact {
|
||||
public_key: PublicKey;
|
||||
public_key: string;
|
||||
name: string | null;
|
||||
type: number;
|
||||
flags: number;
|
||||
@@ -64,7 +53,7 @@ export interface Contact {
|
||||
}
|
||||
|
||||
export interface Channel {
|
||||
key: ChannelKey;
|
||||
key: string;
|
||||
name: string;
|
||||
is_hashtag: boolean;
|
||||
on_radio: boolean;
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
* Repeaters (type=2) always show 🛜 with a gray background.
|
||||
*/
|
||||
|
||||
// Contact type constants (matches backend)
|
||||
export const CONTACT_TYPE_REPEATER = 2;
|
||||
import { CONTACT_TYPE_REPEATER } from '../types';
|
||||
|
||||
// Repeater avatar styling
|
||||
const REPEATER_AVATAR = {
|
||||
|
||||
@@ -13,7 +13,7 @@ const LAST_MESSAGE_KEY = 'remoteterm-lastMessageTime';
|
||||
const SORT_ORDER_KEY = 'remoteterm-sortOrder';
|
||||
|
||||
export type ConversationTimes = Record<string, number>;
|
||||
export type SortOrder = 'recent' | 'alpha';
|
||||
type SortOrder = 'recent' | 'alpha';
|
||||
|
||||
// In-memory cache of last message times (loaded from server on init)
|
||||
let lastMessageTimesCache: ConversationTimes = {};
|
||||
|
||||
@@ -43,6 +43,3 @@ export function clearLocalStorageFavorites(): void {
|
||||
// localStorage might be disabled
|
||||
}
|
||||
}
|
||||
|
||||
// Re-export the Favorite type for convenience
|
||||
export type { Favorite };
|
||||
|
||||
@@ -14,7 +14,7 @@ const PUBKEY_PREFIX_LENGTH = 12;
|
||||
* Extract the 12-character prefix from a public key.
|
||||
* Works with both full keys and existing prefixes.
|
||||
*/
|
||||
export function getPubkeyPrefix(key: string): string {
|
||||
function getPubkeyPrefix(key: string): string {
|
||||
return key.slice(0, PUBKEY_PREFIX_LENGTH);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Conversation } from '../types';
|
||||
|
||||
export interface ParsedHashConversation {
|
||||
interface ParsedHashConversation {
|
||||
type: 'channel' | 'contact' | 'raw' | 'map' | 'visualizer';
|
||||
name: string;
|
||||
/** For map view: public key prefix to focus on */
|
||||
|
||||
Reference in New Issue
Block a user