mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-12 02:35:42 +00:00
fix: fixed activity aggregation and new user tracking (#230)
This commit is contained in:
@@ -1,208 +1,82 @@
|
|||||||
/* Character Activity Component Styles */
|
|
||||||
.characterActivity {
|
|
||||||
&Dialog {
|
|
||||||
background-color: var(--surface-card);
|
|
||||||
color: var(--text-color);
|
|
||||||
width: 80vw;
|
|
||||||
max-width: 650px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set a fixed height for the container so scrolling works properly */
|
|
||||||
&Container {
|
|
||||||
width: 100%;
|
|
||||||
height: 400px; // fixed height to match the DataTable's scrollHeight
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:global {
|
:global {
|
||||||
.p-dialog-content {
|
.p-datatable .p-datatable-thead > tr > th {
|
||||||
padding: 0;
|
background-color: var(--surface-ground);
|
||||||
background-color: var(--surface-card);
|
padding: 0.5rem;
|
||||||
border-bottom-left-radius: 0;
|
text-align: center;
|
||||||
border-bottom-right-radius: 0;
|
white-space: normal;
|
||||||
max-height: 60vh;
|
overflow: visible;
|
||||||
overflow: hidden;
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-datatable .p-datatable-tbody > tr > td {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-datatable {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-datatable-wrapper {
|
.p-datatable-wrapper {
|
||||||
border-radius: 0;
|
border: none !important;
|
||||||
overflow: auto !important;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
height: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background: var(--surface-section);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--surface-border);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--text-color-secondary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-datatable-table {
|
|
||||||
width: 100% !important;
|
|
||||||
table-layout: fixed !important;
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
/* Empty message styling */
|
|
||||||
.p-datatable-emptymessage td {
|
|
||||||
text-align: center;
|
|
||||||
padding: 2rem;
|
|
||||||
color: var(--text-color-secondary);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Character info styles */
|
.spinnerContainer {
|
||||||
.characterInfo {
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.columnHeader {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.75rem; /* text-xs */
|
||||||
|
white-space: normal !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.numericColumnHeader {
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataTable {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cellContent {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
padding: 0.25rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.characterPortrait {
|
.characterInfo {
|
||||||
width: 1.25rem;
|
|
||||||
height: 1.25rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.characterNameContainer {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.characterName {
|
.characterName {
|
||||||
display: flex;
|
|
||||||
font-weight: 500;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.333;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nameText {
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
font-weight: 500;
|
width: 100%;
|
||||||
display: inline;
|
|
||||||
max-width: 180px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.corporationTicker {
|
.characterTicker {
|
||||||
color: var(--text-color-secondary);
|
|
||||||
opacity: 0.8;
|
|
||||||
display: inline;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
max-width: 60px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
|
||||||
|
|
||||||
.allianceTicker {
|
|
||||||
color: var(--text-color-secondary);
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Cell styling */
|
|
||||||
.characterNameCell {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 2px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.activityValueCell {
|
.numericValueCell {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
font-size: 0.75rem; /* text-xs */
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.333;
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Column styles */
|
|
||||||
.characterColumn {
|
|
||||||
min-width: 200px;
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.numericColumn {
|
|
||||||
width: 20%;
|
|
||||||
text-align: center;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Empty state message */
|
|
||||||
.emptyMessage {
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-color-secondary);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loading container */
|
|
||||||
.loadingContainer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 400px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loadingText {
|
|
||||||
margin-top: 1rem;
|
|
||||||
color: var(--text-color-secondary);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Error message styling */
|
|
||||||
.errorMessage {
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--red-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Table row compact style */
|
|
||||||
.tableRowCompact {
|
|
||||||
font-size: 12px !important;
|
|
||||||
line-height: 1.333 !important;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,13 +2,10 @@ import { useState, useEffect, useMemo } from 'react';
|
|||||||
import { Dialog } from 'primereact/dialog';
|
import { Dialog } from 'primereact/dialog';
|
||||||
import { DataTable } from 'primereact/datatable';
|
import { DataTable } from 'primereact/datatable';
|
||||||
import { Column } from 'primereact/column';
|
import { Column } from 'primereact/column';
|
||||||
import classes from './CharacterActivity.module.scss';
|
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
import { ProgressSpinner } from 'primereact/progressspinner';
|
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||||
|
import classes from './CharacterActivity.module.scss';
|
||||||
|
|
||||||
/**
|
|
||||||
* Summary of a character's activity
|
|
||||||
*/
|
|
||||||
export interface ActivitySummary {
|
export interface ActivitySummary {
|
||||||
character_id: string;
|
character_id: string;
|
||||||
character_name: string;
|
character_name: string;
|
||||||
@@ -28,29 +25,26 @@ interface CharacterActivityProps {
|
|||||||
onHide: () => void;
|
onHide: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRowClassName = () => [classes.tableRowCompact, 'p-selectable-row'];
|
const getRowClassName = () => ['text-xs leading-tight', 'p-selectable-row'];
|
||||||
|
|
||||||
const renderCharacterTemplate = (rowData: ActivitySummary) => {
|
const renderCharacterTemplate = (rowData: ActivitySummary) => {
|
||||||
|
const displayName = rowData.is_user ? rowData.user_name : rowData.character_name;
|
||||||
|
const ticker = rowData.corporation_ticker;
|
||||||
|
const allianceTicker = rowData.alliance_ticker ? `[${rowData.alliance_ticker}]` : '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.characterNameCell}>
|
<div className={classes.cellContent}>
|
||||||
|
<div className="w-6 h-6 rounded-full overflow-hidden flex-shrink-0 mr-3">
|
||||||
|
<img src={rowData.portrait_url} alt={displayName} className="w-full h-full object-cover" />
|
||||||
|
</div>
|
||||||
<div className={classes.characterInfo}>
|
<div className={classes.characterInfo}>
|
||||||
<div className={classes.characterPortrait}>
|
<div className={classes.characterName}>
|
||||||
<img src={rowData.portrait_url} alt={rowData.character_name} className="w-full h-full object-cover" />
|
<span className="font-medium text-text-color">{displayName}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.characterNameContainer}>
|
<div className={classes.characterTicker}>
|
||||||
<div className={classes.characterName}>
|
<span className="text-text-color-secondary text-xs">
|
||||||
{rowData.is_user ? (
|
[{ticker}] {allianceTicker}
|
||||||
<>
|
</span>
|
||||||
<span className={classes.nameText}>{rowData.user_name}</span>
|
|
||||||
<span className={classes.corporationTicker}>[{rowData.corporation_ticker}]</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span className={classes.nameText}>{rowData.character_name}</span>
|
|
||||||
<span className={classes.corporationTicker}>[{rowData.corporation_ticker}]</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -58,19 +52,15 @@ const renderCharacterTemplate = (rowData: ActivitySummary) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderValueTemplate = (rowData: ActivitySummary, field: keyof ActivitySummary) => {
|
const renderValueTemplate = (rowData: ActivitySummary, field: keyof ActivitySummary) => {
|
||||||
return <div className={classes.activityValueCell}>{rowData[field] as number}</div>;
|
return <div className={`${classes.numericValueCell} tabular-nums`}>{rowData[field] as number}</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Component that displays character activity in a dialog.
|
|
||||||
*/
|
|
||||||
export const CharacterActivity = ({ visible, onHide }: CharacterActivityProps) => {
|
export const CharacterActivity = ({ visible, onHide }: CharacterActivityProps) => {
|
||||||
const { data } = useMapRootState();
|
const { data } = useMapRootState();
|
||||||
const { characterActivityData } = data;
|
const { characterActivityData } = data;
|
||||||
const [localActivity, setLocalActivity] = useState<ActivitySummary[]>([]);
|
const [localActivity, setLocalActivity] = useState<ActivitySummary[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
// Use the new structure directly
|
|
||||||
const activity = useMemo(() => {
|
const activity = useMemo(() => {
|
||||||
return characterActivityData?.activity || [];
|
return characterActivityData?.activity || [];
|
||||||
}, [characterActivityData]);
|
}, [characterActivityData]);
|
||||||
@@ -80,77 +70,93 @@ export const CharacterActivity = ({ visible, onHide }: CharacterActivityProps) =
|
|||||||
setLoading(characterActivityData?.loading !== false);
|
setLoading(characterActivityData?.loading !== false);
|
||||||
}, [activity, characterActivityData]);
|
}, [activity, characterActivityData]);
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center h-[400px] w-full">
|
||||||
|
<ProgressSpinner className={classes.spinnerContainer} strokeWidth="4" />
|
||||||
|
<div className="mt-4 text-text-color-secondary text-sm">Loading character activity data...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localActivity.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="p-8 text-center text-text-color-secondary italic">No character activity data available</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTable
|
||||||
|
value={localActivity}
|
||||||
|
scrollable
|
||||||
|
scrollHeight="400px"
|
||||||
|
resizableColumns
|
||||||
|
columnResizeMode="fit"
|
||||||
|
className="w-full"
|
||||||
|
tableClassName={classes.dataTable}
|
||||||
|
emptyMessage="No character activity data available"
|
||||||
|
sortField="passages"
|
||||||
|
sortOrder={-1}
|
||||||
|
responsiveLayout="scroll"
|
||||||
|
size="small"
|
||||||
|
rowClassName={getRowClassName}
|
||||||
|
rowHover
|
||||||
|
>
|
||||||
|
<Column
|
||||||
|
field="character_name"
|
||||||
|
header="Character"
|
||||||
|
body={renderCharacterTemplate}
|
||||||
|
sortable
|
||||||
|
headerStyle={{ minWidth: '75px', padding: '0.5rem', height: 'auto', overflow: 'visible' }}
|
||||||
|
bodyStyle={{ minWidth: '75px' }}
|
||||||
|
className={classes.characterColumn}
|
||||||
|
headerClassName={`${classes.columnHeader} ${classes.characterHeader}`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Column
|
||||||
|
field="passages"
|
||||||
|
header="Passages"
|
||||||
|
body={rowData => renderValueTemplate(rowData, 'passages')}
|
||||||
|
sortable
|
||||||
|
headerStyle={{ width: '120px', textAlign: 'center', padding: '0.5rem', height: 'auto', overflow: 'visible' }}
|
||||||
|
bodyStyle={{ width: '120px', textAlign: 'center' }}
|
||||||
|
className={classes.numericColumn}
|
||||||
|
headerClassName={`${classes.columnHeader} ${classes.numericColumnHeader}`}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
field="connections"
|
||||||
|
header="Connections"
|
||||||
|
body={rowData => renderValueTemplate(rowData, 'connections')}
|
||||||
|
sortable
|
||||||
|
headerStyle={{ width: '120px', textAlign: 'center', padding: '0.5rem', height: 'auto', overflow: 'visible' }}
|
||||||
|
bodyStyle={{ width: '120px', textAlign: 'center' }}
|
||||||
|
className={classes.numericColumn}
|
||||||
|
headerClassName={`${classes.columnHeader} ${classes.numericColumnHeader}`}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
field="signatures"
|
||||||
|
header="Signatures"
|
||||||
|
body={rowData => renderValueTemplate(rowData, 'signatures')}
|
||||||
|
sortable
|
||||||
|
headerStyle={{ width: '120px', textAlign: 'center', padding: '0.5rem', height: 'auto', overflow: 'visible' }}
|
||||||
|
bodyStyle={{ width: '120px', textAlign: 'center' }}
|
||||||
|
className={classes.numericColumn}
|
||||||
|
headerClassName={`${classes.columnHeader} ${classes.numericColumnHeader}`}
|
||||||
|
/>
|
||||||
|
</DataTable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
header="Character Activity"
|
header="Character Activity"
|
||||||
visible={visible}
|
visible={visible}
|
||||||
className={classes.characterActivityDialog}
|
className="bg-surface-card text-text-color w-11/12 max-w-[600px]"
|
||||||
onHide={onHide}
|
onHide={onHide}
|
||||||
dismissableMask
|
dismissableMask
|
||||||
draggable={false}
|
|
||||||
resizable={false}
|
|
||||||
closable
|
|
||||||
modal={true}
|
|
||||||
>
|
>
|
||||||
<div className={classes.characterActivityContainer}>
|
<div className="w-full h-[400px] flex flex-col overflow-hidden p-0 m-0">{renderContent()}</div>
|
||||||
{loading && (
|
|
||||||
<div className={classes.loadingContainer}>
|
|
||||||
<ProgressSpinner style={{ width: '50px', height: '50px' }} strokeWidth="4" />
|
|
||||||
<div className={classes.loadingText}>Loading character activity data...</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!loading && localActivity.length === 0 && (
|
|
||||||
<div className={classes.emptyMessage}>No character activity data available</div>
|
|
||||||
)}
|
|
||||||
{!loading && localActivity.length > 0 && (
|
|
||||||
<DataTable
|
|
||||||
value={localActivity}
|
|
||||||
className={classes.characterActivityDatatable}
|
|
||||||
scrollable
|
|
||||||
scrollHeight="400px"
|
|
||||||
emptyMessage="No character activity data available"
|
|
||||||
sortField="passages"
|
|
||||||
sortOrder={-1}
|
|
||||||
responsiveLayout="scroll"
|
|
||||||
size="small"
|
|
||||||
rowClassName={getRowClassName}
|
|
||||||
rowHover
|
|
||||||
>
|
|
||||||
<Column
|
|
||||||
field="character_name"
|
|
||||||
header="Character"
|
|
||||||
body={renderCharacterTemplate}
|
|
||||||
sortable
|
|
||||||
className={classes.characterColumn}
|
|
||||||
headerClassName={`${classes.headerCharacter} text-xs`}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
field="passages"
|
|
||||||
header="Passages"
|
|
||||||
body={rowData => renderValueTemplate(rowData, 'passages')}
|
|
||||||
sortable
|
|
||||||
className={classes.numericColumn}
|
|
||||||
headerClassName={`${classes.headerStandard} text-xs`}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
field="connections"
|
|
||||||
header="Connections"
|
|
||||||
body={rowData => renderValueTemplate(rowData, 'connections')}
|
|
||||||
sortable
|
|
||||||
className={classes.numericColumn}
|
|
||||||
headerClassName={`${classes.headerStandard} text-xs`}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
field="signatures"
|
|
||||||
header="Signatures"
|
|
||||||
body={rowData => renderValueTemplate(rowData, 'signatures')}
|
|
||||||
sortable
|
|
||||||
className={classes.numericColumn}
|
|
||||||
headerClassName={`${classes.headerStandard} text-xs`}
|
|
||||||
/>
|
|
||||||
</DataTable>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,40 +1,3 @@
|
|||||||
/* TrackAndFollow Dialog Styles */
|
.trackFollowHeader {
|
||||||
.trackFollowDialog {
|
background-color: var(--surface-ground);
|
||||||
width: 500px;
|
}
|
||||||
|
|
||||||
:global {
|
|
||||||
/* Dialog content */
|
|
||||||
.p-dialog-content {
|
|
||||||
padding: 0;
|
|
||||||
max-height: 70vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Character grid styles */
|
|
||||||
.characterGrid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid var(--surface-border);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.characterGridHeader {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 80px 80px 1fr;
|
|
||||||
background-color: var(--surface-section);
|
|
||||||
border-bottom: 1px solid var(--surface-border);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: var(--text-color);
|
|
||||||
padding: 2px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.characterGridBody {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useState, useEffect, useMemo } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import { Dialog } from 'primereact/dialog';
|
import { Dialog } from 'primereact/dialog';
|
||||||
import { VirtualScroller } from 'primereact/virtualscroller';
|
import { VirtualScroller } from 'primereact/virtualscroller';
|
||||||
import classes from './TrackAndFollow.module.scss';
|
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||||
import { TrackingCharacterWrapper } from './TrackingCharacterWrapper';
|
import { TrackingCharacterWrapper } from './TrackingCharacterWrapper';
|
||||||
import { TrackingCharacter } from './types';
|
import { TrackingCharacter } from './types';
|
||||||
|
import classes from './TrackAndFollow.module.scss';
|
||||||
|
|
||||||
interface TrackAndFollowProps {
|
interface TrackAndFollowProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@@ -28,30 +28,30 @@ export const TrackAndFollow = ({ visible, onHide }: TrackAndFollowProps) => {
|
|||||||
const characters = useMemo(() => trackingCharactersData || [], [trackingCharactersData]);
|
const characters = useMemo(() => trackingCharactersData || [], [trackingCharactersData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!visible || characters.length === 0) {
|
if (trackingCharactersData) {
|
||||||
return;
|
const newTrackedCharacters = trackingCharactersData
|
||||||
}
|
.filter(character => character.tracked)
|
||||||
const tracked = characters.filter(char => char.tracked).map(char => char.id);
|
.map(character => character.id);
|
||||||
setTrackedCharacters(tracked);
|
|
||||||
|
|
||||||
const followed = characters.find(char => char.followed);
|
setTrackedCharacters(newTrackedCharacters);
|
||||||
setFollowedCharacter(followed ? followed.id : null);
|
|
||||||
}, [visible, characters]);
|
const followedChar = trackingCharactersData.find(character => character.followed);
|
||||||
|
|
||||||
|
if (followedChar?.id !== followedCharacter) {
|
||||||
|
setFollowedCharacter(followedChar?.id || null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [followedCharacter, trackingCharactersData]);
|
||||||
|
|
||||||
const handleTrackToggle = (characterId: string) => {
|
const handleTrackToggle = (characterId: string) => {
|
||||||
setTrackedCharacters(prev => {
|
const isCurrentlyTracked = trackedCharacters.includes(characterId);
|
||||||
if (!prev.includes(characterId)) {
|
|
||||||
return [...prev, characterId];
|
if (isCurrentlyTracked) {
|
||||||
}
|
setTrackedCharacters(prev => prev.filter(id => id !== characterId));
|
||||||
if (followedCharacter === characterId) {
|
} else {
|
||||||
setFollowedCharacter(null);
|
setTrackedCharacters(prev => [...prev, characterId]);
|
||||||
outCommand({
|
}
|
||||||
type: OutCommand.toggleFollow,
|
|
||||||
data: { 'character-id': characterId },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return prev.filter(id => id !== characterId);
|
|
||||||
});
|
|
||||||
outCommand({
|
outCommand({
|
||||||
type: OutCommand.toggleTrack,
|
type: OutCommand.toggleTrack,
|
||||||
data: { 'character-id': characterId },
|
data: { 'character-id': characterId },
|
||||||
@@ -59,14 +59,31 @@ export const TrackAndFollow = ({ visible, onHide }: TrackAndFollowProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleFollowToggle = (characterId: string) => {
|
const handleFollowToggle = (characterId: string) => {
|
||||||
if (followedCharacter !== characterId && !trackedCharacters.includes(characterId)) {
|
const isCurrentlyFollowed = followedCharacter === characterId;
|
||||||
|
const isCurrentlyTracked = trackedCharacters.includes(characterId);
|
||||||
|
|
||||||
|
// If not followed and not tracked, we need to track it first
|
||||||
|
if (!isCurrentlyFollowed && !isCurrentlyTracked) {
|
||||||
setTrackedCharacters(prev => [...prev, characterId]);
|
setTrackedCharacters(prev => [...prev, characterId]);
|
||||||
|
|
||||||
|
// Send track command first
|
||||||
outCommand({
|
outCommand({
|
||||||
type: OutCommand.toggleTrack,
|
type: OutCommand.toggleTrack,
|
||||||
data: { 'character-id': characterId },
|
data: { 'character-id': characterId },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Then send follow command after a short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
outCommand({
|
||||||
|
type: OutCommand.toggleFollow,
|
||||||
|
data: { 'character-id': characterId },
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
setFollowedCharacter(prev => (prev === characterId ? null : characterId));
|
|
||||||
|
// Otherwise just toggle follow
|
||||||
outCommand({
|
outCommand({
|
||||||
type: OutCommand.toggleFollow,
|
type: OutCommand.toggleFollow,
|
||||||
data: { 'character-id': characterId },
|
data: { 'character-id': characterId },
|
||||||
@@ -91,24 +108,23 @@ export const TrackAndFollow = ({ visible, onHide }: TrackAndFollowProps) => {
|
|||||||
header={renderHeader()}
|
header={renderHeader()}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onHide={onHide}
|
onHide={onHide}
|
||||||
modal
|
className="w-[500px] bg-surface-card text-text-color"
|
||||||
className={classes.trackFollowDialog}
|
|
||||||
closeOnEscape
|
|
||||||
showHeader={true}
|
|
||||||
closable={true}
|
|
||||||
>
|
>
|
||||||
<div className={classes.characterGrid}>
|
<div className="w-full overflow-hidden">
|
||||||
<div className={classes.characterGridHeader}>
|
<div
|
||||||
|
className={`
|
||||||
|
grid grid-cols-[80px_80px_1fr]
|
||||||
|
${classes.trackFollowHeader}
|
||||||
|
border-b border-surface-border
|
||||||
|
font-normal text-sm text-text-color
|
||||||
|
p-0.5 text-center
|
||||||
|
`}
|
||||||
|
>
|
||||||
<div>Track</div>
|
<div>Track</div>
|
||||||
<div>Follow</div>
|
<div>Follow</div>
|
||||||
<div>Character</div>
|
<div className="text-center">Character</div>
|
||||||
</div>
|
</div>
|
||||||
<VirtualScroller
|
<VirtualScroller items={characters} itemSize={48} itemTemplate={rowTemplate} className="h-72 w-full" />
|
||||||
items={characters}
|
|
||||||
itemSize={48}
|
|
||||||
itemTemplate={rowTemplate}
|
|
||||||
className={`${classes.characterGridBody} h-72 w-full`}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,118 +1,10 @@
|
|||||||
/* Character grid row styles */
|
.characterRow {
|
||||||
.characterGridRow {
|
border-color: var(--surface-border);
|
||||||
display: grid;
|
border-width: 0 0 1px 0;
|
||||||
grid-template-columns: 80px 80px 1fr;
|
border-style: solid;
|
||||||
border-bottom: 1px solid var(--surface-border);
|
opacity: 0.5;
|
||||||
padding: 2px;
|
|
||||||
align-items: center;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
min-height: 32px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--surface-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.gridCellTrack,
|
|
||||||
.gridCellFollow {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 2px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gridCellCharacter {
|
|
||||||
padding: 2px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Character info styles */
|
|
||||||
.characterInfo {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
min-height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.characterPortrait {
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.characterDetails {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
overflow: hidden;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.characterName {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
max-width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkboxContainer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radioContainer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.portraitImage {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.corporationTicker {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
color: var(--text-color-secondary, #9ca3af);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.allianceTicker {
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
color: var(--text-color-secondary, #9ca3af);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive styles */
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.characterGridRow {
|
|
||||||
grid-template-columns: 60px 60px 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.characterPortrait {
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { TrackingCharacter } from './types';
|
import { TrackingCharacter } from './types';
|
||||||
import { WdCheckbox } from '@/hooks/Mapper/components/ui-kit/WdCheckbox/WdCheckbox';
|
import { WdCheckbox } from '@/hooks/Mapper/components/ui-kit/WdCheckbox/WdCheckbox';
|
||||||
import WdRadioButton from '@/hooks/Mapper/components/ui-kit/WdRadioButton';
|
import WdRadioButton from '@/hooks/Mapper/components/ui-kit/WdRadioButton';
|
||||||
import classes from './TrackingCharacterWrapper.module.scss';
|
|
||||||
import { TooltipPosition, WdTooltipWrapper } from '../../../ui-kit';
|
import { TooltipPosition, WdTooltipWrapper } from '../../../ui-kit';
|
||||||
|
import classes from './TrackingCharacterWrapper.module.scss';
|
||||||
|
|
||||||
interface TrackingCharacterWrapperProps {
|
interface TrackingCharacterWrapperProps {
|
||||||
character: TrackingCharacter;
|
character: TrackingCharacter;
|
||||||
@@ -23,35 +23,46 @@ export const TrackingCharacterWrapper = ({
|
|||||||
const followRadioId = `follow-${character.id}`;
|
const followRadioId = `follow-${character.id}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.characterGridRow}>
|
<div
|
||||||
<div className={classes.gridCellTrack}>
|
className={`
|
||||||
|
grid grid-cols-[80px_80px_1fr]
|
||||||
|
${classes.characterRow}
|
||||||
|
p-0.5 items-center transition-colors duration-200 min-h-8 hover:bg-surface-hover
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-center items-center p-0.5 text-center">
|
||||||
<WdTooltipWrapper content="Track this character on the map" position={TooltipPosition.top}>
|
<WdTooltipWrapper content="Track this character on the map" position={TooltipPosition.top}>
|
||||||
<div className={classes.checkboxContainer}>
|
<div className="flex justify-center items-center w-full">
|
||||||
<WdCheckbox id={trackCheckboxId} label="" value={isTracked} onChange={() => onTrackToggle()} />
|
<WdCheckbox id={trackCheckboxId} label="" value={isTracked} onChange={() => onTrackToggle()} />
|
||||||
</div>
|
</div>
|
||||||
</WdTooltipWrapper>
|
</WdTooltipWrapper>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.gridCellFollow}>
|
<div className="flex justify-center items-center p-0.5 text-center">
|
||||||
<WdTooltipWrapper content="Follow this character's movements on the map" position={TooltipPosition.top}>
|
<WdTooltipWrapper content="Follow this character's movements on the map" position={TooltipPosition.top}>
|
||||||
<div className={classes.radioContainer}>
|
<div className="flex justify-center items-center w-full">
|
||||||
<WdRadioButton
|
<div onClick={onFollowToggle} className="cursor-pointer">
|
||||||
id={followRadioId}
|
<WdRadioButton id={followRadioId} name="followed_character" checked={isFollowed} onChange={() => {}} />
|
||||||
name="followed_character"
|
</div>
|
||||||
checked={isFollowed}
|
|
||||||
onChange={() => onFollowToggle()}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</WdTooltipWrapper>
|
</WdTooltipWrapper>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.gridCellCharacter}>
|
<div className="p-0.5 flex items-center justify-center">
|
||||||
<div className={classes.characterInfo}>
|
<div className="flex items-center gap-3 w-full overflow-hidden min-h-8 justify-center">
|
||||||
<div className={classes.characterPortrait}>
|
<div className="w-8 h-8 rounded-full overflow-hidden flex-shrink-0">
|
||||||
<img src={character.portrait_url} alt={character.name} className={classes.portraitImage} />
|
<img src={character.portrait_url} alt={character.name} className="w-full h-full object-cover" />
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.characterDetails}>
|
<div className="flex items-center overflow-hidden flex-nowrap whitespace-nowrap">
|
||||||
<span className={classes.characterName}>{character.name}</span>
|
<span
|
||||||
<span className={classes.corporationTicker}>[{character.corporation_ticker}]</span>
|
className={`
|
||||||
{character.alliance_ticker && <span className={classes.allianceTicker}>[{character.alliance_ticker}]</span>}
|
text-sm text-color-color whitespace-nowrap overflow-hidden text-ellipsis max-w-[150px]
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{character.name}
|
||||||
|
</span>
|
||||||
|
<span className="ml-2 text-text-color-secondary text-sm">[{character.corporation_ticker}]</span>
|
||||||
|
{character.alliance_ticker && (
|
||||||
|
<span className="ml-1 text-text-color-secondary text-sm">[{character.alliance_ticker}]</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,17 +13,17 @@ export const useTrackAndFollowHandlers = () => {
|
|||||||
* Handle hiding the track and follow dialog
|
* Handle hiding the track and follow dialog
|
||||||
*/
|
*/
|
||||||
const handleHideTracking = useCallback(() => {
|
const handleHideTracking = useCallback(() => {
|
||||||
// Update local state to hide the dialog
|
// Send the command to the server first
|
||||||
update(state => ({
|
|
||||||
...state,
|
|
||||||
showTrackAndFollow: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Send the command to the server
|
|
||||||
outCommand({
|
outCommand({
|
||||||
type: OutCommand.hideTracking,
|
type: OutCommand.hideTracking,
|
||||||
data: {},
|
data: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Then update local state to hide the dialog
|
||||||
|
update(state => ({
|
||||||
|
...state,
|
||||||
|
showTrackAndFollow: false,
|
||||||
|
}));
|
||||||
}, [outCommand, update]);
|
}, [outCommand, update]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -146,6 +146,30 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
|||||||
userSettingsUpdated(data as CommandUserSettingsUpdated);
|
userSettingsUpdated(data as CommandUserSettingsUpdated);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Commands.showTracking:
|
||||||
|
// This command is handled by the TrackAndFollow component
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Commands.hideTracking:
|
||||||
|
// This command is handled by the TrackAndFollow component
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Commands.showActivity:
|
||||||
|
// This command is handled by the CharacterActivity component
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Commands.hideActivity:
|
||||||
|
// This command is handled by the CharacterActivity component
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Commands.toggleTrack:
|
||||||
|
// This command is handled by the TrackAndFollow component
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Commands.toggleFollow:
|
||||||
|
// This command is handled by the TrackAndFollow component
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
|
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -63,19 +63,59 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
|||||||
end
|
end
|
||||||
|
|
||||||
update :track do
|
update :track do
|
||||||
change(set_attribute(:tracked, true))
|
accept [:map_id, :character_id]
|
||||||
|
argument :map_id, :string, allow_nil?: false
|
||||||
|
argument :character_id, :uuid, allow_nil?: false
|
||||||
|
|
||||||
|
# Load the record first
|
||||||
|
load do
|
||||||
|
filter expr(map_id == ^arg(:map_id) and character_id == ^arg(:character_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Only update the tracked field
|
||||||
|
change set_attribute(:tracked, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
update :untrack do
|
update :untrack do
|
||||||
change(set_attribute(:tracked, false))
|
accept [:map_id, :character_id]
|
||||||
|
argument :map_id, :string, allow_nil?: false
|
||||||
|
argument :character_id, :uuid, allow_nil?: false
|
||||||
|
|
||||||
|
# Load the record first
|
||||||
|
load do
|
||||||
|
filter expr(map_id == ^arg(:map_id) and character_id == ^arg(:character_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Only update the tracked field
|
||||||
|
change set_attribute(:tracked, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
update :follow do
|
update :follow do
|
||||||
change(set_attribute(:followed, true))
|
accept [:map_id, :character_id]
|
||||||
|
argument :map_id, :string, allow_nil?: false
|
||||||
|
argument :character_id, :uuid, allow_nil?: false
|
||||||
|
|
||||||
|
# Load the record first
|
||||||
|
load do
|
||||||
|
filter expr(map_id == ^arg(:map_id) and character_id == ^arg(:character_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Only update the followed field
|
||||||
|
change set_attribute(:followed, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
update :unfollow do
|
update :unfollow do
|
||||||
change(set_attribute(:followed, false))
|
accept [:map_id, :character_id]
|
||||||
|
argument :map_id, :string, allow_nil?: false
|
||||||
|
argument :character_id, :uuid, allow_nil?: false
|
||||||
|
|
||||||
|
# Load the record first
|
||||||
|
load do
|
||||||
|
filter expr(map_id == ^arg(:map_id) and character_id == ^arg(:character_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Only update the followed field
|
||||||
|
change set_attribute(:followed, false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -257,4 +257,51 @@ defmodule WandererApp.Character do
|
|||||||
corporation: true
|
corporation: true
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Finds a character by EVE ID from a user's active characters.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
- `current_user`: The current user struct
|
||||||
|
- `character_eve_id`: The EVE ID of the character to find
|
||||||
|
|
||||||
|
## Returns
|
||||||
|
- `{:ok, character}` if the character is found
|
||||||
|
- `{:error, :character_not_found}` if the character is not found
|
||||||
|
"""
|
||||||
|
def find_character_by_eve_id(current_user, character_eve_id) do
|
||||||
|
{:ok, all_user_characters} =
|
||||||
|
WandererApp.Api.Character.active_by_user(%{user_id: current_user.id})
|
||||||
|
|
||||||
|
case Enum.find(all_user_characters, fn char ->
|
||||||
|
"#{char.eve_id}" == "#{character_eve_id}"
|
||||||
|
end) do
|
||||||
|
nil ->
|
||||||
|
{:error, :character_not_found}
|
||||||
|
|
||||||
|
character ->
|
||||||
|
{:ok, character}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Finds a character by character ID from a user's characters.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
- `current_user`: The current user struct
|
||||||
|
- `char_id`: The character ID to find
|
||||||
|
|
||||||
|
## Returns
|
||||||
|
- `{:ok, character}` if the character is found
|
||||||
|
- `{:error, :character_not_found}` if the character is not found
|
||||||
|
"""
|
||||||
|
def find_user_character(current_user, char_id) do
|
||||||
|
case Enum.find(current_user.characters, &("#{&1.id}" == "#{char_id}")) do
|
||||||
|
nil ->
|
||||||
|
{:error, :character_not_found}
|
||||||
|
|
||||||
|
char ->
|
||||||
|
{:ok, char}
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
defmodule WandererApp.Utils.CharacterUtil do
|
defmodule WandererApp.Character.Activity do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Utility functions for character-related operations.
|
Functions for processing and managing character activity data.
|
||||||
"""
|
"""
|
||||||
|
require Logger
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Finds a followed character ID from a list of character settings and activities.
|
Finds a followed character ID from a list of character settings and activities.
|
||||||
@@ -99,7 +100,6 @@ defmodule WandererApp.Utils.CharacterUtil do
|
|||||||
|> group_by_user_id()
|
|> group_by_user_id()
|
||||||
|> process_users_activity(character_settings, user_characters, current_user)
|
|> process_users_activity(character_settings, user_characters, current_user)
|
||||||
|> sort_by_timestamp()
|
|> sort_by_timestamp()
|
||||||
|> group_and_select_most_active()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp group_by_user_id(activities) do
|
defp group_by_user_id(activities) do
|
||||||
@@ -191,7 +191,7 @@ defmodule WandererApp.Utils.CharacterUtil do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_character_details(char_id, char_activities, user_characters, true) do
|
defp get_character_details(char_id, _char_activities, user_characters, true) do
|
||||||
Enum.find(user_characters, fn char ->
|
Enum.find(user_characters, fn char ->
|
||||||
char.id == char_id || to_string(char.eve_id) == char_id
|
char.id == char_id || to_string(char.eve_id) == char_id
|
||||||
end)
|
end)
|
||||||
@@ -207,7 +207,13 @@ defmodule WandererApp.Utils.CharacterUtil do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_activity_entry(char_details, char_activities, current_user, is_current_user, user_id) do
|
defp build_activity_entry(
|
||||||
|
char_details,
|
||||||
|
char_activities,
|
||||||
|
current_user,
|
||||||
|
is_current_user,
|
||||||
|
_user_id
|
||||||
|
) do
|
||||||
%{
|
%{
|
||||||
character_id: char_details.eve_id || char_details.id,
|
character_id: char_details.eve_id || char_details.id,
|
||||||
character_name: char_details.name,
|
character_name: char_details.name,
|
||||||
@@ -222,9 +228,6 @@ defmodule WandererApp.Utils.CharacterUtil do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_system_info(activities, key, default),
|
|
||||||
do: Map.get(List.first(activities) || %{}, key, default)
|
|
||||||
|
|
||||||
defp sum_activity(activities, key),
|
defp sum_activity(activities, key),
|
||||||
do: activities |> Enum.map(&Map.get(&1, key, 0)) |> Enum.sum()
|
do: activities |> Enum.map(&Map.get(&1, key, 0)) |> Enum.sum()
|
||||||
|
|
||||||
@@ -238,21 +241,4 @@ defmodule WandererApp.Utils.CharacterUtil do
|
|||||||
defp sort_by_timestamp(activities) do
|
defp sort_by_timestamp(activities) do
|
||||||
Enum.sort_by(activities, & &1.timestamp, {:desc, DateTime})
|
Enum.sort_by(activities, & &1.timestamp, {:desc, DateTime})
|
||||||
end
|
end
|
||||||
|
|
||||||
defp group_and_select_most_active(activities) do
|
|
||||||
activities
|
|
||||||
|> Enum.group_by(&Map.get(&1, :user_id, "unknown"))
|
|
||||||
|> Enum.map(fn {_user_id, user_activities} ->
|
|
||||||
user_activities
|
|
||||||
|> Enum.sort_by(
|
|
||||||
fn activity ->
|
|
||||||
Map.get(activity, :passages, 0) +
|
|
||||||
Map.get(activity, :connections, 0) +
|
|
||||||
Map.get(activity, :signatures, 0)
|
|
||||||
end,
|
|
||||||
:desc
|
|
||||||
)
|
|
||||||
|> List.first()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@@ -7,7 +7,6 @@ defmodule WandererApp.Map do
|
|||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
alias WandererApp.Utils.EVEUtil
|
alias WandererApp.Utils.EVEUtil
|
||||||
alias WandererApp.Utils.CharacterUtil
|
|
||||||
|
|
||||||
defstruct map_id: nil,
|
defstruct map_id: nil,
|
||||||
name: nil,
|
name: nil,
|
||||||
@@ -527,13 +526,12 @@ defmodule WandererApp.Map do
|
|||||||
defp _maybe_limit_list(list, limit), do: Enum.take(list, limit)
|
defp _maybe_limit_list(list, limit), do: Enum.take(list, limit)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets raw character activity data for a map.
|
Returns the raw activity data that can be processed by WandererApp.Character.Activity.
|
||||||
Returns the raw activity data that can be processed by CharacterUtil.
|
|
||||||
Only includes characters that are on the map's ACL.
|
Only includes characters that are on the map's ACL.
|
||||||
"""
|
"""
|
||||||
def get_character_activity(map_id) do
|
def get_character_activity(map_id) do
|
||||||
{:ok, map} = WandererApp.Api.Map.by_id(map_id)
|
{:ok, map} = WandererApp.Api.Map.by_id(map_id)
|
||||||
map = Ash.load!(map, :acls)
|
_map_with_acls = Ash.load!(map, :acls)
|
||||||
|
|
||||||
{:ok, jumps} = WandererApp.Api.MapChainPassages.by_map_id(%{map_id: map_id})
|
{:ok, jumps} = WandererApp.Api.MapChainPassages.by_map_id(%{map_id: map_id})
|
||||||
thirty_days_ago = DateTime.utc_now() |> DateTime.add(-30 * 24 * 3600, :second)
|
thirty_days_ago = DateTime.utc_now() |> DateTime.add(-30 * 24 * 3600, :second)
|
||||||
|
|||||||
@@ -37,18 +37,63 @@ defmodule WandererApp.MapCharacterSettingsRepo do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def track(settings), do: settings |> WandererApp.Api.MapCharacterSettings.track()
|
def track(settings) do
|
||||||
def untrack(settings), do: settings |> WandererApp.Api.MapCharacterSettings.untrack()
|
# Only update the tracked field, preserving other fields
|
||||||
|
WandererApp.Api.MapCharacterSettings.track(%{
|
||||||
|
map_id: settings.map_id,
|
||||||
|
character_id: settings.character_id
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
def track!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.track!()
|
def untrack(settings) do
|
||||||
def untrack!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.untrack!()
|
# Only update the tracked field, preserving other fields
|
||||||
|
WandererApp.Api.MapCharacterSettings.untrack(%{
|
||||||
|
map_id: settings.map_id,
|
||||||
|
character_id: settings.character_id
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
def follow(settings), do: settings |> WandererApp.Api.MapCharacterSettings.follow()
|
def track!(settings),
|
||||||
def unfollow(settings), do: settings |> WandererApp.Api.MapCharacterSettings.unfollow()
|
do:
|
||||||
|
WandererApp.Api.MapCharacterSettings.track!(%{
|
||||||
|
map_id: settings.map_id,
|
||||||
|
character_id: settings.character_id
|
||||||
|
})
|
||||||
|
|
||||||
def follow!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.follow!()
|
def untrack!(settings),
|
||||||
def unfollow!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.unfollow!()
|
do:
|
||||||
|
WandererApp.Api.MapCharacterSettings.untrack!(%{
|
||||||
|
map_id: settings.map_id,
|
||||||
|
character_id: settings.character_id
|
||||||
|
})
|
||||||
|
|
||||||
|
def follow(settings) do
|
||||||
|
WandererApp.Api.MapCharacterSettings.follow(%{
|
||||||
|
map_id: settings.map_id,
|
||||||
|
character_id: settings.character_id
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def unfollow(settings) do
|
||||||
|
WandererApp.Api.MapCharacterSettings.unfollow(%{
|
||||||
|
map_id: settings.map_id,
|
||||||
|
character_id: settings.character_id
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def follow!(settings),
|
||||||
|
do:
|
||||||
|
WandererApp.Api.MapCharacterSettings.follow!(%{
|
||||||
|
map_id: settings.map_id,
|
||||||
|
character_id: settings.character_id
|
||||||
|
})
|
||||||
|
|
||||||
|
def unfollow!(settings),
|
||||||
|
do:
|
||||||
|
WandererApp.Api.MapCharacterSettings.unfollow!(%{
|
||||||
|
map_id: settings.map_id,
|
||||||
|
character_id: settings.character_id
|
||||||
|
})
|
||||||
|
|
||||||
def destroy!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.destroy!()
|
def destroy!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.destroy!()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ defmodule WandererAppWeb.MapAPIController do
|
|||||||
|
|
||||||
alias WandererApp.Api
|
alias WandererApp.Api
|
||||||
alias WandererApp.Api.Character
|
alias WandererApp.Api.Character
|
||||||
alias WandererApp.Api.MapSolarSystem
|
|
||||||
alias WandererApp.MapSystemRepo
|
alias WandererApp.MapSystemRepo
|
||||||
alias WandererApp.MapCharacterSettingsRepo
|
alias WandererApp.MapCharacterSettingsRepo
|
||||||
|
|
||||||
|
|||||||
@@ -103,28 +103,66 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
|||||||
}
|
}
|
||||||
} = socket
|
} = socket
|
||||||
) do
|
) do
|
||||||
|
# Get all character settings to preserve followed state
|
||||||
|
{:ok, all_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
|
||||||
|
|
||||||
|
# Get tracked characters
|
||||||
{:ok, map_characters} = WandererApp.Maps.get_tracked_map_characters(map_id, current_user)
|
{:ok, map_characters} = WandererApp.Maps.get_tracked_map_characters(map_id, current_user)
|
||||||
|
|
||||||
user_character_eve_ids = map_characters |> Enum.map(& &1.eve_id)
|
user_character_eve_ids = map_characters |> Enum.map(& &1.eve_id)
|
||||||
|
|
||||||
|
# Update socket assigns but don't affect followed state
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> assign(user_characters: user_character_eve_ids)
|
||||||
|
|> assign(has_tracked_characters?: has_tracked_characters?(user_character_eve_ids))
|
||||||
|
|
||||||
|
# Get the map with ACLs for building tracking data
|
||||||
|
{:ok, map} = WandererApp.Api.Map.by_id(map_id)
|
||||||
|
map = Ash.load!(map, :acls)
|
||||||
|
|
||||||
|
# Get characters that have access to the map
|
||||||
|
{:ok, %{characters: characters_with_access}} =
|
||||||
|
WandererApp.Maps.load_characters(map, all_settings, current_user.id)
|
||||||
|
|
||||||
|
{:ok, latest_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
|
||||||
|
|
||||||
|
# Create tracking data that preserves followed state
|
||||||
|
tracking_data =
|
||||||
|
characters_with_access
|
||||||
|
|> Enum.map(fn char ->
|
||||||
|
# Find existing settings to preserve followed state
|
||||||
|
# Use the latest settings to ensure we have the most up-to-date followed state
|
||||||
|
setting = Enum.find(latest_settings, &(&1.character_id == char.id))
|
||||||
|
# Keep the existing tracked and followed states
|
||||||
|
tracked = if setting, do: setting.tracked, else: false
|
||||||
|
followed = if setting, do: setting.followed, else: false
|
||||||
|
|
||||||
|
%{
|
||||||
|
id: char.eve_id,
|
||||||
|
name: char.name,
|
||||||
|
corporation_ticker: char.corporation_ticker,
|
||||||
|
alliance_ticker: Map.get(char, :alliance_ticker, ""),
|
||||||
|
portrait_url: EVEUtil.get_portrait_url(char.eve_id),
|
||||||
|
tracked: tracked,
|
||||||
|
followed: followed
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
socket
|
socket
|
||||||
|> assign(user_characters: user_character_eve_ids)
|
|
||||||
|> assign(has_tracked_characters?: has_tracked_characters?(user_character_eve_ids))
|
|
||||||
|> MapEventHandler.push_map_event(
|
|> MapEventHandler.push_map_event(
|
||||||
"init",
|
"tracking_characters_data",
|
||||||
%{
|
%{characters: tracking_data}
|
||||||
user_characters: user_character_eve_ids,
|
|
||||||
reset: false
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_server_event(event, socket),
|
def handle_server_event(event, socket),
|
||||||
do: MapCoreEventHandler.handle_server_event(event, socket)
|
do: MapCoreEventHandler.handle_server_event(event, socket)
|
||||||
|
|
||||||
|
# UI Event Handlers
|
||||||
def handle_ui_event(
|
def handle_ui_event(
|
||||||
"toggle_track",
|
"toggle_track",
|
||||||
%{"character-id" => character_eve_id},
|
%{"character-id" => clicked_char_id},
|
||||||
%{
|
%{
|
||||||
assigns: %{
|
assigns: %{
|
||||||
map_id: map_id,
|
map_id: map_id,
|
||||||
@@ -133,59 +171,87 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
|||||||
}
|
}
|
||||||
} = socket
|
} = socket
|
||||||
) do
|
) do
|
||||||
# Get all user characters
|
# First, get all existing settings to preserve states
|
||||||
{:ok, all_user_characters} =
|
{:ok, all_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
|
||||||
WandererApp.Api.Character.active_by_user(%{user_id: current_user.id})
|
|
||||||
|
|
||||||
# Find the character that was clicked
|
# Save the followed character ID and settings before making any changes
|
||||||
character =
|
{followed_character_id, _followed_character_settings} =
|
||||||
Enum.find(all_user_characters, fn char ->
|
all_settings
|
||||||
"#{char.eve_id}" == "#{character_eve_id}"
|
|> Enum.find(& &1.followed)
|
||||||
end)
|
|> case do
|
||||||
|
nil -> {nil, nil}
|
||||||
if character do
|
setting -> {setting.character_id, setting}
|
||||||
# Get existing settings for this character on this map
|
|
||||||
case WandererApp.MapCharacterSettingsRepo.get_by_map(map_id, character.id) do
|
|
||||||
{:ok, existing_settings} ->
|
|
||||||
# Toggle the tracked status
|
|
||||||
if existing_settings.tracked do
|
|
||||||
# Untrack the character
|
|
||||||
{:ok, updated_settings} =
|
|
||||||
WandererApp.MapCharacterSettingsRepo.untrack(existing_settings)
|
|
||||||
|
|
||||||
# If the character was also followed, unfollow it
|
|
||||||
if updated_settings.followed do
|
|
||||||
{:ok, _} = WandererApp.MapCharacterSettingsRepo.unfollow(updated_settings)
|
|
||||||
end
|
|
||||||
|
|
||||||
:ok = untrack_characters([character], map_id)
|
|
||||||
:ok = remove_characters([character], map_id)
|
|
||||||
|
|
||||||
if only_tracked_characters do
|
|
||||||
Process.send_after(self(), :not_all_characters_tracked, 10)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
# Track the character
|
|
||||||
{:ok, _} = WandererApp.MapCharacterSettingsRepo.track(existing_settings)
|
|
||||||
:ok = track_characters([character], map_id, true)
|
|
||||||
:ok = add_characters([character], map_id, true)
|
|
||||||
Process.send_after(self(), %{event: :refresh_user_characters}, 10)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:error, :not_found} ->
|
|
||||||
# Create new settings with tracked=true
|
|
||||||
{:ok, _} =
|
|
||||||
WandererApp.MapCharacterSettingsRepo.create(%{
|
|
||||||
character_id: character.id,
|
|
||||||
map_id: map_id,
|
|
||||||
tracked: true,
|
|
||||||
followed: false
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, tracking_data} = get_tracking_data(map_id, current_user)
|
# Find the character we're toggling
|
||||||
|
with {:ok, character} <-
|
||||||
|
WandererApp.Character.find_character_by_eve_id(current_user, clicked_char_id),
|
||||||
|
{:ok, updated_settings} <-
|
||||||
|
toggle_character_tracking(character, map_id, only_tracked_characters) do
|
||||||
|
# Get the map with ACLs
|
||||||
|
{:ok, map} = WandererApp.Api.Map.by_id(map_id)
|
||||||
|
map = Ash.load!(map, :acls)
|
||||||
|
|
||||||
|
# Get characters that have access to the map
|
||||||
|
{:ok, %{characters: characters_with_access}} =
|
||||||
|
WandererApp.Maps.load_characters(map, all_settings, current_user.id)
|
||||||
|
|
||||||
|
# If there was a followed character before, check if it's still followed
|
||||||
|
# Only check if we're not toggling the followed character itself
|
||||||
|
if followed_character_id && followed_character_id != character.id do
|
||||||
|
# Get the current settings for the followed character
|
||||||
|
case WandererApp.MapCharacterSettingsRepo.get_by_map(map_id, followed_character_id) do
|
||||||
|
{:ok, current_settings} ->
|
||||||
|
# If it's not followed anymore, follow it again
|
||||||
|
if !current_settings.followed do
|
||||||
|
{:ok, _} = WandererApp.MapCharacterSettingsRepo.follow(current_settings)
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get updated settings after potentially restoring followed state
|
||||||
|
{:ok, new_all_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
|
||||||
|
|
||||||
|
# Create tracking data for characters with access to the map
|
||||||
|
tracking_data =
|
||||||
|
characters_with_access
|
||||||
|
|> Enum.map(fn char ->
|
||||||
|
# For the character being toggled, use the updated settings
|
||||||
|
setting =
|
||||||
|
if "#{char.eve_id}" == "#{clicked_char_id}" do
|
||||||
|
updated_settings
|
||||||
|
else
|
||||||
|
# For other characters, use the updated settings
|
||||||
|
current_setting = Enum.find(new_all_settings, &(&1.character_id == char.id))
|
||||||
|
|
||||||
|
# If this was the previously followed character, make sure it's still followed
|
||||||
|
if followed_character_id && followed_character_id == char.id &&
|
||||||
|
current_setting && !current_setting.followed do
|
||||||
|
# This character was previously followed but is no longer followed
|
||||||
|
# Restore the followed state
|
||||||
|
%{current_setting | followed: true}
|
||||||
|
else
|
||||||
|
current_setting
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tracked = if setting, do: setting.tracked, else: false
|
||||||
|
followed = if setting, do: setting.followed, else: false
|
||||||
|
|
||||||
|
%{
|
||||||
|
id: char.eve_id,
|
||||||
|
name: char.name,
|
||||||
|
corporation_ticker: char.corporation_ticker,
|
||||||
|
alliance_ticker: Map.get(char, :alliance_ticker, ""),
|
||||||
|
portrait_url: EVEUtil.get_portrait_url(char.eve_id),
|
||||||
|
tracked: tracked,
|
||||||
|
followed: followed
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
# Send the updated tracking data to the client
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> MapEventHandler.push_map_event(
|
|> MapEventHandler.push_map_event(
|
||||||
@@ -193,7 +259,8 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
|||||||
%{characters: tracking_data}
|
%{characters: tracking_data}
|
||||||
)}
|
)}
|
||||||
else
|
else
|
||||||
{:noreply, socket}
|
_ ->
|
||||||
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -207,12 +274,109 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
|||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|
|> MapEventHandler.push_map_event(
|
||||||
|
"show_tracking",
|
||||||
|
%{}
|
||||||
|
)
|
||||||
|> MapEventHandler.push_map_event(
|
|> MapEventHandler.push_map_event(
|
||||||
"tracking_characters_data",
|
"tracking_characters_data",
|
||||||
%{characters: tracking_data}
|
%{characters: tracking_data}
|
||||||
)}
|
)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_ui_event(
|
||||||
|
"toggle_follow",
|
||||||
|
%{"character-id" => clicked_char_id},
|
||||||
|
%{assigns: %{current_user: current_user, map_id: map_id}} = socket
|
||||||
|
) do
|
||||||
|
# Get all settings before the operation to see the followed state
|
||||||
|
{:ok, all_settings_before} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
|
||||||
|
followed_before = all_settings_before |> Enum.find(& &1.followed)
|
||||||
|
|
||||||
|
# Check if the clicked character is already followed
|
||||||
|
is_already_followed =
|
||||||
|
followed_before && "#{followed_before.character_id}" == "#{clicked_char_id}"
|
||||||
|
|
||||||
|
# Use find_character_by_eve_id from WandererApp.Character
|
||||||
|
with {:ok, clicked_char} <-
|
||||||
|
WandererApp.Character.find_character_by_eve_id(current_user, clicked_char_id),
|
||||||
|
{:ok, _updated_settings} <-
|
||||||
|
toggle_character_follow(map_id, clicked_char, is_already_followed) do
|
||||||
|
# Get the state after the toggle_character_follow operation
|
||||||
|
{:ok, all_settings_after} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
|
||||||
|
_followed_after = all_settings_after |> Enum.find(& &1.followed)
|
||||||
|
|
||||||
|
# Build tracking data
|
||||||
|
{:ok, tracking_data} = build_tracking_data(map_id, current_user)
|
||||||
|
|
||||||
|
# Get the followed character in the tracking data
|
||||||
|
_followed_in_tracking = tracking_data |> Enum.find(& &1.followed)
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> MapEventHandler.push_map_event("tracking_characters_data", %{characters: tracking_data})}
|
||||||
|
else
|
||||||
|
error ->
|
||||||
|
Logger.error("Failed to toggle follow: #{inspect(error)}")
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_ui_event(
|
||||||
|
"show_activity",
|
||||||
|
_,
|
||||||
|
%{assigns: %{map_id: map_id, current_user: current_user}} = socket
|
||||||
|
) do
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> MapEventHandler.push_map_event(
|
||||||
|
"character_activity_data",
|
||||||
|
%{activity: [], loading: true}
|
||||||
|
)
|
||||||
|
|
||||||
|
task =
|
||||||
|
Task.async(fn ->
|
||||||
|
try do
|
||||||
|
result =
|
||||||
|
WandererApp.Character.Activity.process_character_activity(map_id, current_user)
|
||||||
|
|
||||||
|
{:activity_data, result}
|
||||||
|
rescue
|
||||||
|
e ->
|
||||||
|
Logger.error("Error processing character activity: #{inspect(e)}")
|
||||||
|
Logger.error("#{Exception.format_stacktrace()}")
|
||||||
|
{:activity_data, []}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:noreply, socket |> assign(:character_activity_task, task)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_ui_event("hide_activity", _, socket),
|
||||||
|
do: {:noreply, socket |> assign(show_activity?: false)}
|
||||||
|
|
||||||
|
def handle_ui_event("add_character", _, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> MapEventHandler.push_map_event("show_tracking", %{})}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_ui_event(
|
||||||
|
"add_character",
|
||||||
|
_,
|
||||||
|
%{assigns: %{user_permissions: %{track_character: false}}} = socket
|
||||||
|
) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> put_flash(
|
||||||
|
:error,
|
||||||
|
"You don't have permissions to track characters. Please contact administrator."
|
||||||
|
)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_ui_event(event, body, socket),
|
||||||
|
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
|
||||||
|
|
||||||
defp get_tracking_data(map_id, current_user) do
|
defp get_tracking_data(map_id, current_user) do
|
||||||
# Get character settings for this map
|
# Get character settings for this map
|
||||||
{:ok, character_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
|
{:ok, character_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
|
||||||
@@ -232,80 +396,23 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
|||||||
|
|
||||||
# Create tracking data for characters with access to the map
|
# Create tracking data for characters with access to the map
|
||||||
{:ok,
|
{:ok,
|
||||||
characters_with_access
|
characters_with_access
|
||||||
|> Enum.map(fn char ->
|
|> Enum.map(fn char ->
|
||||||
# Find settings for this character if they exist
|
# Find settings for this character if they exist
|
||||||
setting = Enum.find(character_settings, &(&1.character_id == char.id))
|
setting = Enum.find(character_settings, &(&1.character_id == char.id))
|
||||||
tracked = if setting, do: setting.tracked, else: false
|
tracked = if setting, do: setting.tracked, else: false
|
||||||
followed = if setting, do: setting.followed, else: false
|
followed = if setting, do: setting.followed, else: false
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: char.eve_id,
|
id: char.eve_id,
|
||||||
name: char.name,
|
name: char.name,
|
||||||
corporation_ticker: char.corporation_ticker,
|
corporation_ticker: char.corporation_ticker,
|
||||||
alliance_ticker: Map.get(char, :alliance_ticker, ""),
|
alliance_ticker: Map.get(char, :alliance_ticker, ""),
|
||||||
portrait_url: EVEUtil.get_portrait_url(char.eve_id),
|
portrait_url: EVEUtil.get_portrait_url(char.eve_id),
|
||||||
tracked: tracked,
|
tracked: tracked,
|
||||||
followed: followed
|
followed: followed
|
||||||
}
|
}
|
||||||
end)}
|
end)}
|
||||||
end
|
|
||||||
|
|
||||||
def handle_ui_event(
|
|
||||||
"toggle_follow",
|
|
||||||
%{"character-id" => clicked_char_id},
|
|
||||||
%{assigns: %{map_id: map_id, current_user: current_user}} = socket
|
|
||||||
) do
|
|
||||||
with {:ok, clicked_char} <- find_user_character(current_user, clicked_char_id),
|
|
||||||
{:ok, updated_settings} <- toggle_character_follow(map_id, clicked_char),
|
|
||||||
{:ok, tracking_data} <- build_tracking_data(map_id, current_user) do
|
|
||||||
{:noreply,
|
|
||||||
socket
|
|
||||||
|> MapEventHandler.push_map_event("tracking_characters_data", %{characters: tracking_data})}
|
|
||||||
else
|
|
||||||
_ -> {:noreply, socket}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_ui_event(
|
|
||||||
"show_activity",
|
|
||||||
_,
|
|
||||||
%{assigns: %{map_id: map_id, current_user: current_user}} = socket
|
|
||||||
) do
|
|
||||||
socket =
|
|
||||||
socket
|
|
||||||
|> MapEventHandler.push_map_event(
|
|
||||||
"character_activity_data",
|
|
||||||
%{activity: [], loading: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
task =
|
|
||||||
Task.async(fn ->
|
|
||||||
try do
|
|
||||||
result =
|
|
||||||
WandererApp.Utils.CharacterUtil.process_character_activity(map_id, current_user)
|
|
||||||
|
|
||||||
{:activity_data, result}
|
|
||||||
rescue
|
|
||||||
e ->
|
|
||||||
Logger.error("Error processing character activity: #{inspect(e)}")
|
|
||||||
Logger.error("#{Exception.format_stacktrace()}")
|
|
||||||
{:activity_data, []}
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
{:noreply, socket |> assign(:character_activity_task, task)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_ui_event("hide_activity", _, socket),
|
|
||||||
do: {:noreply, socket |> assign(show_activity?: false)}
|
|
||||||
|
|
||||||
def handle_ui_event(event, params, socket) do
|
|
||||||
Logger.debug(fn ->
|
|
||||||
"unhandled event in MapCharactersEventHandler: #{inspect(event)} with params: #{inspect(params)}"
|
|
||||||
end)
|
|
||||||
|
|
||||||
{:noreply, socket}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_tracked_characters?([]), do: false
|
def has_tracked_characters?([]), do: false
|
||||||
@@ -472,13 +579,23 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
|||||||
{:ok, %{characters: characters_with_access}} =
|
{:ok, %{characters: characters_with_access}} =
|
||||||
load_map_characters(map, character_settings, current_user)
|
load_map_characters(map, character_settings, current_user)
|
||||||
|
|
||||||
|
socket = init_tracking_state(socket, current_user)
|
||||||
|
|
||||||
|
needs_tracking_setup =
|
||||||
|
needs_tracking_setup?(characters_with_access, character_settings, user_permissions)
|
||||||
|
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> assign(:needs_tracking_setup, needs_tracking_setup)
|
||||||
|
|> then(fn socket ->
|
||||||
|
if needs_tracking_setup do
|
||||||
|
socket
|
||||||
|
else
|
||||||
|
socket
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
socket
|
socket
|
||||||
|> init_tracking_state(
|
|
||||||
characters_with_access,
|
|
||||||
character_settings,
|
|
||||||
current_user,
|
|
||||||
user_permissions
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_map_with_acls(map_id) do
|
defp get_map_with_acls(map_id) do
|
||||||
@@ -491,54 +608,26 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
|||||||
WandererApp.Maps.load_characters(map, character_settings, current_user.id)
|
WandererApp.Maps.load_characters(map, character_settings, current_user.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp init_tracking_state(
|
def init_tracking_state(socket, current_user) do
|
||||||
socket,
|
|
||||||
characters_with_access,
|
|
||||||
character_settings,
|
|
||||||
current_user,
|
|
||||||
user_permissions
|
|
||||||
) do
|
|
||||||
user_character_eve_ids = current_user.characters |> Enum.map(& &1.eve_id)
|
user_character_eve_ids = current_user.characters |> Enum.map(& &1.eve_id)
|
||||||
has_tracked_characters? = has_tracked_characters?(user_character_eve_ids)
|
has_tracked_characters? = has_tracked_characters?(user_character_eve_ids)
|
||||||
|
|
||||||
needs_tracking_setup =
|
|
||||||
needs_tracking_setup?(
|
|
||||||
socket.assigns.only_tracked_characters,
|
|
||||||
characters_with_access,
|
|
||||||
character_settings,
|
|
||||||
user_permissions
|
|
||||||
)
|
|
||||||
|
|
||||||
socket
|
socket
|
||||||
|> assign(
|
|> assign(
|
||||||
has_tracked_characters?: has_tracked_characters?,
|
has_tracked_characters?: has_tracked_characters?,
|
||||||
user_characters: user_character_eve_ids,
|
user_characters: user_character_eve_ids
|
||||||
needs_tracking_setup: needs_tracking_setup
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp needs_tracking_setup?(
|
def needs_tracking_setup?(characters, character_settings, user_permissions) do
|
||||||
only_tracked_characters,
|
|
||||||
characters,
|
|
||||||
character_settings,
|
|
||||||
user_permissions
|
|
||||||
) do
|
|
||||||
tracked_count =
|
|
||||||
characters
|
|
||||||
|> Enum.count(fn char ->
|
|
||||||
setting = Enum.find(character_settings, &(&1.character_id == char.id))
|
|
||||||
setting && setting.tracked
|
|
||||||
end)
|
|
||||||
|
|
||||||
untracked_count =
|
untracked_count =
|
||||||
characters
|
characters
|
||||||
|> Enum.count(fn char ->
|
|> Enum.count(fn char ->
|
||||||
setting = Enum.find(character_settings, &(&1.character_id == char.id))
|
setting = Enum.find(character_settings, &(&1.character_id == char.id))
|
||||||
not (setting && setting.tracked)
|
setting == nil || !setting.tracked
|
||||||
end)
|
end)
|
||||||
|
|
||||||
user_permissions.track_character &&
|
untracked_count > 0 && user_permissions.track_character
|
||||||
((untracked_count > 0 && only_tracked_characters) || tracked_count == 0)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@@ -597,39 +686,6 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
|
||||||
Shows the tracking dialog with the given characters.
|
|
||||||
"""
|
|
||||||
def show_tracking_dialog(socket, characters_with_access, character_settings) do
|
|
||||||
tracking_data =
|
|
||||||
Enum.map(characters_with_access, fn char ->
|
|
||||||
setting = Enum.find(character_settings, &(&1.character_id == char.id))
|
|
||||||
tracked = if setting, do: setting.tracked, else: false
|
|
||||||
followed = if setting, do: setting.followed, else: false
|
|
||||||
|
|
||||||
%{
|
|
||||||
id: char.id,
|
|
||||||
name: char.name,
|
|
||||||
portrait_url: EVEUtil.get_portrait_url(char.eve_id, 64),
|
|
||||||
corporation_ticker: char.corporation_ticker,
|
|
||||||
alliance_ticker: Map.get(char, :alliance_ticker, ""),
|
|
||||||
tracked: tracked,
|
|
||||||
followed: followed
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
|
|
||||||
socket
|
|
||||||
|> push_event("map_event", %{
|
|
||||||
type: "show_tracking",
|
|
||||||
body: %{}
|
|
||||||
})
|
|
||||||
|> push_event("map_event", %{
|
|
||||||
type: "tracking_characters_data",
|
|
||||||
body: %{characters: tracking_data}
|
|
||||||
})
|
|
||||||
|> assign(:show_tracking, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_activity_data(socket, activity_data) do
|
def handle_activity_data(socket, activity_data) do
|
||||||
socket
|
socket
|
||||||
|> MapEventHandler.push_map_event("character_activity_data", %{
|
|> MapEventHandler.push_map_event("character_activity_data", %{
|
||||||
@@ -680,54 +736,83 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp find_user_character(current_user, char_id) do
|
defp toggle_character_follow(map_id, clicked_char, is_already_followed) do
|
||||||
case Enum.find(current_user.characters, &("#{&1.id}" == "#{char_id}")) do
|
|
||||||
nil -> {:error, :character_not_found}
|
|
||||||
char -> {:ok, char}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp toggle_character_follow(map_id, clicked_char) do
|
|
||||||
with {:ok, clicked_char_settings} <-
|
with {:ok, clicked_char_settings} <-
|
||||||
WandererApp.MapCharacterSettingsRepo.get_by_map(map_id, clicked_char.id),
|
WandererApp.MapCharacterSettingsRepo.get_by_map(map_id, clicked_char.id) do
|
||||||
{:ok, settings} <- update_follow_status(map_id, clicked_char, clicked_char_settings) do
|
if is_already_followed do
|
||||||
{:ok, settings}
|
# If already followed, just unfollow without affecting other characters
|
||||||
|
{:ok, updated_settings} =
|
||||||
|
WandererApp.MapCharacterSettingsRepo.unfollow(clicked_char_settings)
|
||||||
|
|
||||||
|
{:ok, updated_settings}
|
||||||
|
else
|
||||||
|
# Normal follow toggle
|
||||||
|
{:ok, settings} = update_follow_status(map_id, clicked_char, clicked_char_settings)
|
||||||
|
{:ok, settings}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
{:error, :not_found} ->
|
||||||
|
# Character not found in settings, create new settings
|
||||||
|
update_follow_status(map_id, clicked_char, nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp update_follow_status(map_id, clicked_char, nil) do
|
defp update_follow_status(map_id, clicked_char, nil) do
|
||||||
# Create new settings with tracked=true and followed=true
|
# Create new settings with tracked=true and followed=true
|
||||||
WandererApp.MapCharacterSettingsRepo.create(%{
|
# If we're following this character, unfollow all others first
|
||||||
character_id: clicked_char.id,
|
:ok = maybe_unfollow_others(map_id, clicked_char.id, true)
|
||||||
map_id: map_id,
|
|
||||||
tracked: true,
|
result =
|
||||||
followed: true
|
WandererApp.MapCharacterSettingsRepo.create(%{
|
||||||
})
|
character_id: clicked_char.id,
|
||||||
|
map_id: map_id,
|
||||||
|
tracked: true,
|
||||||
|
followed: true
|
||||||
|
})
|
||||||
|
|
||||||
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
defp update_follow_status(map_id, clicked_char, clicked_char_settings) do
|
defp update_follow_status(map_id, clicked_char, clicked_char_settings) do
|
||||||
|
# Toggle the followed state
|
||||||
followed = !clicked_char_settings.followed
|
followed = !clicked_char_settings.followed
|
||||||
|
|
||||||
with :ok <- maybe_unfollow_others(map_id, clicked_char.id, followed),
|
# Only unfollow other characters if we're explicitly following this character
|
||||||
:ok <- maybe_track_character(clicked_char_settings, followed),
|
# This prevents unfollowing other characters when just tracking a character
|
||||||
{:ok, settings} <- update_follow(clicked_char_settings, followed) do
|
if followed do
|
||||||
{:ok, settings}
|
# We're following this character, so unfollow all others
|
||||||
|
:ok = maybe_unfollow_others(map_id, clicked_char.id, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# If we're following, make sure the character is also tracked
|
||||||
|
:ok = maybe_track_character(clicked_char_settings, followed)
|
||||||
|
|
||||||
|
# Update the follow status
|
||||||
|
{:ok, settings} = update_follow(clicked_char_settings, followed)
|
||||||
|
|
||||||
|
{:ok, settings}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_unfollow_others(_map_id, _char_id, false), do: :ok
|
defp maybe_unfollow_others(_map_id, _char_id, false), do: :ok
|
||||||
|
|
||||||
defp maybe_unfollow_others(map_id, char_id, true) do
|
defp maybe_unfollow_others(map_id, char_id, true) do
|
||||||
|
# This function should only be called when explicitly following a character,
|
||||||
|
# not when tracking a character. It unfollows all other characters when
|
||||||
|
# setting a character as followed.
|
||||||
|
|
||||||
{:ok, all_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
|
{:ok, all_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
|
||||||
|
|
||||||
|
# Unfollow other characters
|
||||||
all_settings
|
all_settings
|
||||||
|> Enum.filter(&(&1.character_id != char_id && &1.followed))
|
|> Enum.filter(&(&1.character_id != char_id && &1.followed))
|
||||||
|> Enum.each(&WandererApp.MapCharacterSettingsRepo.unfollow/1)
|
|> Enum.each(fn setting ->
|
||||||
|
WandererApp.MapCharacterSettingsRepo.unfollow(setting)
|
||||||
|
end)
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_track_character(settings, false), do: :ok
|
defp maybe_track_character(_settings, false), do: :ok
|
||||||
|
|
||||||
defp maybe_track_character(settings, true) do
|
defp maybe_track_character(settings, true) do
|
||||||
if not settings.tracked do
|
if not settings.tracked do
|
||||||
@@ -750,15 +835,15 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
|||||||
Enum.map(characters_with_access, fn char ->
|
Enum.map(characters_with_access, fn char ->
|
||||||
setting = Enum.find(character_settings, &(&1.character_id == char.id))
|
setting = Enum.find(character_settings, &(&1.character_id == char.id))
|
||||||
tracked = if setting, do: setting.tracked, else: false
|
tracked = if setting, do: setting.tracked, else: false
|
||||||
|
# Important: Preserve the followed state
|
||||||
followed = if setting, do: setting.followed, else: false
|
followed = if setting, do: setting.followed, else: false
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: "#{char.id}",
|
id: char.eve_id,
|
||||||
name: char.name,
|
name: char.name,
|
||||||
eve_id: char.eve_id,
|
|
||||||
portrait_url: EVEUtil.get_portrait_url(char.eve_id),
|
|
||||||
corporation_ticker: char.corporation_ticker,
|
corporation_ticker: char.corporation_ticker,
|
||||||
alliance_ticker: Map.get(char, :alliance_ticker, ""),
|
alliance_ticker: Map.get(char, :alliance_ticker, ""),
|
||||||
|
portrait_url: EVEUtil.get_portrait_url(char.eve_id),
|
||||||
tracked: tracked,
|
tracked: tracked,
|
||||||
followed: followed
|
followed: followed
|
||||||
}
|
}
|
||||||
@@ -767,4 +852,45 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
|||||||
{:ok, tracking_data}
|
{:ok, tracking_data}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Helper function to toggle character tracking
|
||||||
|
defp toggle_character_tracking(character, map_id, _only_tracked_characters) do
|
||||||
|
case WandererApp.MapCharacterSettingsRepo.get_by_map(map_id, character.id) do
|
||||||
|
{:ok, existing_settings} ->
|
||||||
|
if existing_settings.tracked do
|
||||||
|
# Untrack the character
|
||||||
|
{:ok, updated_settings} =
|
||||||
|
WandererApp.MapCharacterSettingsRepo.untrack(existing_settings)
|
||||||
|
|
||||||
|
# If the character was followed, we need to unfollow it too
|
||||||
|
# But we should NOT unfollow other characters
|
||||||
|
if existing_settings.followed do
|
||||||
|
{:ok, final_settings} =
|
||||||
|
WandererApp.MapCharacterSettingsRepo.unfollow(updated_settings)
|
||||||
|
|
||||||
|
{:ok, final_settings}
|
||||||
|
else
|
||||||
|
{:ok, updated_settings}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# Track the character
|
||||||
|
{:ok, updated_settings} =
|
||||||
|
WandererApp.MapCharacterSettingsRepo.track(existing_settings)
|
||||||
|
|
||||||
|
{:ok, updated_settings}
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, :not_found} ->
|
||||||
|
# Create new settings
|
||||||
|
result =
|
||||||
|
WandererApp.MapCharacterSettingsRepo.create(%{
|
||||||
|
character_id: character.id,
|
||||||
|
map_id: map_id,
|
||||||
|
tracked: true,
|
||||||
|
followed: false
|
||||||
|
})
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
|||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
alias WandererAppWeb.{MapEventHandler, MapCharactersEventHandler, MapSystemsEventHandler}
|
alias WandererAppWeb.{MapEventHandler, MapCharactersEventHandler, MapSystemsEventHandler}
|
||||||
alias WandererApp.Utils.EVEUtil
|
|
||||||
|
|
||||||
def handle_server_event(:update_permissions, socket) do
|
def handle_server_event(:update_permissions, socket) do
|
||||||
DebounceAndThrottle.Debounce.apply(
|
DebounceAndThrottle.Debounce.apply(
|
||||||
@@ -175,7 +174,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
|||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_ui_event("toggle_track_" <> character_id, _, socket),
|
def handle_ui_event("toggle_track", %{"character-id" => character_id}, socket),
|
||||||
do:
|
do:
|
||||||
MapCharactersEventHandler.handle_ui_event(
|
MapCharactersEventHandler.handle_ui_event(
|
||||||
"toggle_track",
|
"toggle_track",
|
||||||
@@ -183,7 +182,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
|||||||
socket
|
socket
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_ui_event("toggle_follow_" <> character_id, _, socket),
|
def handle_ui_event("toggle_follow", %{"character-id" => character_id}, socket),
|
||||||
do:
|
do:
|
||||||
MapCharactersEventHandler.handle_ui_event(
|
MapCharactersEventHandler.handle_ui_event(
|
||||||
"toggle_follow",
|
"toggle_follow",
|
||||||
@@ -239,6 +238,11 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
|||||||
|
|
||||||
def handle_ui_event("noop", _, socket), do: {:noreply, socket}
|
def handle_ui_event("noop", _, socket), do: {:noreply, socket}
|
||||||
|
|
||||||
|
def handle_ui_event(event, body, socket) do
|
||||||
|
Logger.debug(fn -> "unhandled map ui event: #{inspect(event)} #{inspect(body)}" end)
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
|
||||||
def handle_ui_event(
|
def handle_ui_event(
|
||||||
_event,
|
_event,
|
||||||
_body,
|
_body,
|
||||||
@@ -256,11 +260,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_ui_event(event, body, socket) do
|
|
||||||
Logger.debug(fn -> "unhandled map ui event: #{inspect(event)} #{inspect(body)}" end)
|
|
||||||
{:noreply, socket}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp maybe_start_map(map_id) do
|
defp maybe_start_map(map_id) do
|
||||||
{:ok, map_server_started} = WandererApp.Cache.lookup("map_#{map_id}:started", false)
|
{:ok, map_server_started} = WandererApp.Cache.lookup("map_#{map_id}:started", false)
|
||||||
|
|
||||||
@@ -544,8 +543,8 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
|||||||
|
|
||||||
# Initialize character tracking
|
# Initialize character tracking
|
||||||
socket =
|
socket =
|
||||||
socket
|
MapCharactersEventHandler.init_character_tracking(
|
||||||
|> MapCharactersEventHandler.init_character_tracking(
|
socket,
|
||||||
map_id,
|
map_id,
|
||||||
%{
|
%{
|
||||||
current_user: current_user,
|
current_user: current_user,
|
||||||
@@ -598,22 +597,4 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
|||||||
user_character_eve_ids |> Enum.member?(character.eve_id)
|
user_character_eve_ids |> Enum.member?(character.eve_id)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp handle_task_result(socket, {:activity_data, activity_data}),
|
|
||||||
do: MapCharactersEventHandler.handle_activity_data(socket, activity_data)
|
|
||||||
|
|
||||||
defp handle_task_result(socket, {:ok, %{type: type} = result})
|
|
||||||
when type in [
|
|
||||||
:character_activity,
|
|
||||||
:character_tracking,
|
|
||||||
:character_settings,
|
|
||||||
:character_location,
|
|
||||||
:character_online,
|
|
||||||
:character_ship,
|
|
||||||
:character_fleet
|
|
||||||
] do
|
|
||||||
MapCharactersEventHandler.handle_character_result(socket, type, result)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp handle_task_result(socket, _), do: socket
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ defmodule WandererAppWeb.MapEventHandler do
|
|||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
alias WandererAppWeb.{
|
alias WandererAppWeb.{
|
||||||
MapActivityEventHandler,
|
|
||||||
MapCharactersEventHandler,
|
MapCharactersEventHandler,
|
||||||
MapConnectionsEventHandler,
|
MapConnectionsEventHandler,
|
||||||
MapCoreEventHandler,
|
MapCoreEventHandler,
|
||||||
@@ -85,7 +84,9 @@ defmodule WandererAppWeb.MapEventHandler do
|
|||||||
|
|
||||||
@map_activity_ui_events [
|
@map_activity_ui_events [
|
||||||
"show_activity",
|
"show_activity",
|
||||||
"hide_activity"
|
"hide_activity",
|
||||||
|
"toggle_follow",
|
||||||
|
"toggle_track"
|
||||||
]
|
]
|
||||||
|
|
||||||
@map_routes_events [
|
@map_routes_events [
|
||||||
@@ -223,48 +224,36 @@ defmodule WandererAppWeb.MapEventHandler do
|
|||||||
def handle_event(socket, event),
|
def handle_event(socket, event),
|
||||||
do: MapCoreEventHandler.handle_server_event(event, socket)
|
do: MapCoreEventHandler.handle_server_event(event, socket)
|
||||||
|
|
||||||
def handle_ui_event(event, body, socket)
|
def handle_ui_event(event, body, socket) do
|
||||||
when event in @map_characters_ui_events,
|
cond do
|
||||||
do: MapCharactersEventHandler.handle_ui_event(event, body, socket)
|
event in @map_characters_ui_events ->
|
||||||
|
MapCharactersEventHandler.handle_ui_event(event, body, socket)
|
||||||
|
|
||||||
def handle_ui_event(event, body, socket)
|
event in @map_system_ui_events ->
|
||||||
when event in @map_system_ui_events,
|
MapSystemsEventHandler.handle_ui_event(event, body, socket)
|
||||||
do: MapSystemsEventHandler.handle_ui_event(event, body, socket)
|
|
||||||
|
|
||||||
def handle_ui_event(event, body, socket)
|
event in @map_connection_ui_events ->
|
||||||
when event in @map_connection_ui_events,
|
MapConnectionsEventHandler.handle_ui_event(event, body, socket)
|
||||||
do: MapConnectionsEventHandler.handle_ui_event(event, body, socket)
|
|
||||||
|
|
||||||
def handle_ui_event(event, body, socket)
|
event in @map_routes_ui_events ->
|
||||||
when event in @map_routes_ui_events,
|
MapRoutesEventHandler.handle_ui_event(event, body, socket)
|
||||||
do: MapRoutesEventHandler.handle_ui_event(event, body, socket)
|
|
||||||
|
|
||||||
def handle_ui_event(event, body, socket)
|
event in @map_signatures_ui_events ->
|
||||||
when event in @map_signatures_ui_events,
|
MapSignaturesEventHandler.handle_ui_event(event, body, socket)
|
||||||
do: MapSignaturesEventHandler.handle_ui_event(event, body, socket)
|
|
||||||
|
|
||||||
def handle_ui_event(event, body, socket)
|
event in @map_structures_ui_events ->
|
||||||
when event in @map_structures_ui_events,
|
MapStructuresEventHandler.handle_ui_event(event, body, socket)
|
||||||
do: MapStructuresEventHandler.handle_ui_event(event, body, socket)
|
|
||||||
|
|
||||||
def handle_ui_event(event, body, socket)
|
event in @map_activity_ui_events ->
|
||||||
when event in @map_activity_ui_events,
|
MapCharactersEventHandler.handle_ui_event(event, body, socket)
|
||||||
do: MapCharactersEventHandler.handle_ui_event(event, body, socket)
|
|
||||||
|
|
||||||
def handle_ui_event(
|
event in @map_kills_ui_events and socket.assigns[:is_subscription_active?] ->
|
||||||
event,
|
MapKillsEventHandler.handle_ui_event(event, body, socket)
|
||||||
body,
|
|
||||||
%{
|
|
||||||
assigns: %{
|
|
||||||
is_subscription_active?: true
|
|
||||||
}
|
|
||||||
} = socket
|
|
||||||
)
|
|
||||||
when event in @map_kills_ui_events,
|
|
||||||
do: MapKillsEventHandler.handle_ui_event(event, body, socket)
|
|
||||||
|
|
||||||
def handle_ui_event(event, body, socket),
|
true ->
|
||||||
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
|
MapCoreEventHandler.handle_ui_event(event, body, socket)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def get_system_static_info(nil), do: nil
|
def get_system_static_info(nil), do: nil
|
||||||
|
|
||||||
|
|||||||
@@ -95,8 +95,9 @@ defmodule WandererAppWeb.MapLive do
|
|||||||
|> WandererAppWeb.MapEventHandler.handle_event(info)}
|
|> WandererAppWeb.MapEventHandler.handle_event(info)}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event(event, body, socket),
|
def handle_event(event, body, socket) do
|
||||||
do: WandererAppWeb.MapEventHandler.handle_ui_event(event, body, socket)
|
WandererAppWeb.MapEventHandler.handle_ui_event(event, body, socket)
|
||||||
|
end
|
||||||
|
|
||||||
defp apply_action(socket, :index, _params) do
|
defp apply_action(socket, :index, _params) do
|
||||||
socket
|
socket
|
||||||
|
|||||||
Reference in New Issue
Block a user