This commit is contained in:
112
src/hooks/useStoryOptions.ts
Normal file
112
src/hooks/useStoryOptions.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { sortStoryOptionsByPriority } from '../data/stateFunctions';
|
||||
import { annotateStoryOptionsWithGoalAffordance } from '../services/storyEngine/goalDirector';
|
||||
import type { GoalStackState, StoryMoment } from '../types';
|
||||
|
||||
const OPTION_PAGE_SIZE = 3;
|
||||
|
||||
export function useStoryOptions(
|
||||
currentStory: StoryMoment | null,
|
||||
goalStack?: GoalStackState | null,
|
||||
) {
|
||||
const [optionWindowStart, setOptionWindowStart] = useState(0);
|
||||
|
||||
const activeOptionPool = useMemo(() => {
|
||||
if (!currentStory) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return sortStoryOptionsByPriority(
|
||||
annotateStoryOptionsWithGoalAffordance(
|
||||
currentStory.options,
|
||||
goalStack,
|
||||
),
|
||||
);
|
||||
}, [currentStory, goalStack]);
|
||||
|
||||
const optionPoolSignature = useMemo(
|
||||
() =>
|
||||
activeOptionPool
|
||||
.map((option) =>
|
||||
[
|
||||
option.functionId,
|
||||
option.actionText,
|
||||
option.text ?? '',
|
||||
option.goalAffordance?.goalId ?? '',
|
||||
option.goalAffordance?.relation ?? '',
|
||||
].join('::'),
|
||||
)
|
||||
.join('||'),
|
||||
[activeOptionPool],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setOptionWindowStart(0);
|
||||
}, [currentStory, optionPoolSignature]);
|
||||
|
||||
const displayedOptions = useMemo(
|
||||
() => {
|
||||
const windowOptions = activeOptionPool.slice(
|
||||
optionWindowStart,
|
||||
optionWindowStart + OPTION_PAGE_SIZE,
|
||||
);
|
||||
if (
|
||||
windowOptions.some(
|
||||
(option) => option.goalAffordance?.relation === 'advance',
|
||||
)
|
||||
) {
|
||||
return windowOptions;
|
||||
}
|
||||
|
||||
const pinnedAdvanceOption =
|
||||
activeOptionPool.find(
|
||||
(option) => option.goalAffordance?.relation === 'advance',
|
||||
) ?? null;
|
||||
if (!pinnedAdvanceOption) {
|
||||
return windowOptions;
|
||||
}
|
||||
|
||||
return [
|
||||
pinnedAdvanceOption,
|
||||
...windowOptions
|
||||
.filter(
|
||||
(option) =>
|
||||
!(
|
||||
option.functionId === pinnedAdvanceOption.functionId
|
||||
&& option.actionText === pinnedAdvanceOption.actionText
|
||||
),
|
||||
)
|
||||
.slice(0, OPTION_PAGE_SIZE - 1),
|
||||
];
|
||||
},
|
||||
[activeOptionPool, optionWindowStart],
|
||||
);
|
||||
|
||||
const canRefreshOptions = activeOptionPool.length > OPTION_PAGE_SIZE;
|
||||
|
||||
const handleRefreshOptions = useCallback(() => {
|
||||
if (activeOptionPool.length <= OPTION_PAGE_SIZE) return;
|
||||
|
||||
const nextStart = optionWindowStart + OPTION_PAGE_SIZE;
|
||||
if (nextStart < activeOptionPool.length) {
|
||||
setOptionWindowStart(nextStart);
|
||||
return;
|
||||
}
|
||||
|
||||
setOptionWindowStart(0);
|
||||
}, [activeOptionPool.length, optionWindowStart]);
|
||||
|
||||
const resetStoryOptions = useCallback(() => {
|
||||
setOptionWindowStart(0);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
activeOptionPool,
|
||||
displayedOptions,
|
||||
optionWindowStart,
|
||||
canRefreshOptions,
|
||||
handleRefreshOptions,
|
||||
resetStoryOptions,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user