From bb58617bf186f22bb0ecddec875dd9f3c9ee4cf8 Mon Sep 17 00:00:00 2001 From: Art Date: Wed, 25 Jan 2023 21:45:02 +0000 Subject: [PATCH] feat: Add action+trigger descriptions & deprecation messages --- src/actions/combat/activate-prayer-action.tsx | 1 + src/actions/combat/activate-spell-actions.tsx | 3 + src/actions/core/buy-item-action.tsx | 1 + src/actions/core/delay-action.tsx | 2 + src/actions/core/equip-food-action.tsx | 1 + src/actions/core/equip-item-action.tsx | 1 + src/actions/core/load-agi-blueprint.tsx | 1 + src/actions/core/sell-item-action.mts | 1 + src/actions/workflow/exec-workflow-action.mts | 2 + src/actions/workflow/set-step-idx-action.mts | 1 + src/lib/public-api-validation.mts | 10 ++- src/public_api/core.d.ts | 6 ++ .../combat/enemy-attack-type-trigger.mts | 1 + .../combat/enemy-special-attack-trigger.tsx | 1 + src/ui/components/trigger-config.tsx | 81 ++++++++----------- .../categorised-node-select.tsx | 32 ++++++-- src/ui/pages/help-page.tsx | 3 +- src/ui/pages/help-page/help-pages.tsx | 8 -- 18 files changed, 93 insertions(+), 63 deletions(-) diff --git a/src/actions/combat/activate-prayer-action.tsx b/src/actions/combat/activate-prayer-action.tsx index db8511c..65faddb 100644 --- a/src/actions/combat/activate-prayer-action.tsx +++ b/src/actions/combat/activate-prayer-action.tsx @@ -15,6 +15,7 @@ defineLocalAction({ return Deactivate prayers; } }, + description: 'Activates/deactivates prayers', execute({prayers}) { const player = game.combat.player; player.disableActivePrayers(); diff --git a/src/actions/combat/activate-spell-actions.tsx b/src/actions/combat/activate-spell-actions.tsx index 3821ad6..3098c31 100644 --- a/src/actions/combat/activate-spell-actions.tsx +++ b/src/actions/combat/activate-spell-actions.tsx @@ -64,6 +64,7 @@ const mkAction = ({media, registry, ...init}: Init): void => { }; mkAction({ + description: 'Cast a spell from the standard spellbook', id: ActionId.CombatCastSpellStd, label: 'Cast Standard spell', media: 'combat/spellbook.svg', @@ -73,6 +74,7 @@ mkAction({ }); mkAction({ + description: 'Cast a spell from the ancient spellbook', id: ActionId.CombatCastSpellAncient, label: 'Cast Ancient spell', media: 'combat/ancient.svg', @@ -82,6 +84,7 @@ mkAction({ }); mkAction({ + description: 'Cast a spell from the archaic spellbook', id: ActionId.CombatCastSpellArchaic, label: 'Cast Archaic spell', media: 'magic/archaic_book.svg', diff --git a/src/actions/core/buy-item-action.tsx b/src/actions/core/buy-item-action.tsx index c966e98..4c90ab4 100644 --- a/src/actions/core/buy-item-action.tsx +++ b/src/actions/core/buy-item-action.tsx @@ -20,6 +20,7 @@ defineLocalAction({ ), + description: 'Buy an item from the shop', execute({item, qty}) { if (!passesLimitCount(item)) { return; diff --git a/src/actions/core/delay-action.tsx b/src/actions/core/delay-action.tsx index f893d1b..7f42e99 100644 --- a/src/actions/core/delay-action.tsx +++ b/src/actions/core/delay-action.tsx @@ -16,6 +16,8 @@ defineLocalAction({ {`${duration.toLocaleString()}ms`} ), + deprecated: 'Deprecated and will be removed soon™. It was a hacky workaround to begin with.', + description: 'Delay the execution of subsequent actions & triggers by n milliseconds', execute: ({duration}) => timer(duration), id: ActionId.CoreDelay, label: 'Wait', diff --git a/src/actions/core/equip-food-action.tsx b/src/actions/core/equip-food-action.tsx index f39f108..0a623fb 100644 --- a/src/actions/core/equip-food-action.tsx +++ b/src/actions/core/equip-food-action.tsx @@ -26,6 +26,7 @@ defineLocalAction({ ), + description: 'Equip food to the first free slot', execute({item, qty}) { const bankQty = game.bank.getQty(item); if (!bankQty) { diff --git a/src/actions/core/equip-item-action.tsx b/src/actions/core/equip-item-action.tsx index 2163d59..d589c07 100644 --- a/src/actions/core/equip-item-action.tsx +++ b/src/actions/core/equip-item-action.tsx @@ -36,6 +36,7 @@ defineLocalAction({ )} ), + description: 'Equip an item. You can set a slot & the equip amount when applicable.', execute({item, qty, slot}) { const bankQty = game.bank.getQty(item); if (!bankQty) { diff --git a/src/actions/core/load-agi-blueprint.tsx b/src/actions/core/load-agi-blueprint.tsx index 6cdaafe..f1d5c34 100644 --- a/src/actions/core/load-agi-blueprint.tsx +++ b/src/actions/core/load-agi-blueprint.tsx @@ -9,6 +9,7 @@ interface Props { defineLocalAction({ category: InternalCategory.CORE, + description: 'Loads an agility blueprint. Make sure you have enough resources & currency!', execute({idx}) { const agi = game.agility; const blueprint = agi.blueprints.get(idx); diff --git a/src/actions/core/sell-item-action.mts b/src/actions/core/sell-item-action.mts index f1c48d6..83e1641 100644 --- a/src/actions/core/sell-item-action.mts +++ b/src/actions/core/sell-item-action.mts @@ -9,6 +9,7 @@ interface Props { defineLocalAction({ category: InternalCategory.CORE, + description: 'This will sell the whole stack', execute({items}) { for (const item of items) { const qty = game.bank.getQty(item); diff --git a/src/actions/workflow/exec-workflow-action.mts b/src/actions/workflow/exec-workflow-action.mts index e0369c2..261c5ff 100644 --- a/src/actions/workflow/exec-workflow-action.mts +++ b/src/actions/workflow/exec-workflow-action.mts @@ -53,6 +53,7 @@ const base = { defineLocalAction({ ...base, + description: 'Run another workflow as an action!', execute({name, stop}, ctx) { const workflow = WorkflowRegistry.inst.workflows .find(wf => wf.name === name); @@ -76,6 +77,7 @@ defineLocalAction({ defineLocalAction({ ...base, + description: 'Similar to "Run workflow", but runs one of the workflows defined at the bottom of the page.', execute({name, stop}, ctx) { const workflow: Workflow | undefined = (ctx!.workflow as WorkflowRefImpl) .getEmbeddedWorkflow(name) diff --git a/src/actions/workflow/set-step-idx-action.mts b/src/actions/workflow/set-step-idx-action.mts index a0c6f1b..b615205 100644 --- a/src/actions/workflow/set-step-idx-action.mts +++ b/src/actions/workflow/set-step-idx-action.mts @@ -8,6 +8,7 @@ interface Props { defineLocalAction({ category: InternalCategory.WORKFLOW, + description: 'Forcibly make the workflow jump to another step to repeat the whole (or part of the) workflow. Note that the step cannot jump to itself.', execContext: true, execute({idx}, ctx) { ctx!.setActiveStepIdx(idx); diff --git a/src/lib/public-api-validation.mts b/src/lib/public-api-validation.mts index d3d8b9c..bbf7f48 100644 --- a/src/lib/public-api-validation.mts +++ b/src/lib/public-api-validation.mts @@ -66,9 +66,17 @@ function isNodeDefinition(v: any): v is (NodeDefinition & Obj) { return false; } - const {compactRender, media, options} = v as Partial; + const { + compactRender, + deprecated, + description, + media, + options, + } = v as Partial; return typeof media === 'string' + && typeIs(deprecated, 'undefined', 'boolean', 'string') + && isUndefinedOr(description, 'string') && isUndefinedOr(compactRender, 'function') && (options === undefined || (Array.isArray(options) && options.every(isNodeOption))); } diff --git a/src/public_api/core.d.ts b/src/public_api/core.d.ts index c918247..5683643 100644 --- a/src/public_api/core.d.ts +++ b/src/public_api/core.d.ts @@ -53,6 +53,12 @@ export interface NodeDefinition extends Referenceable { */ compactRender?: (props: T) => VNode | null | undefined; + /** Flag this node for deprecation. Optionally include a deprecation message */ + deprecated?: true | string; + + /** Node description */ + description?: string; + /** The icon */ media: string; diff --git a/src/triggers/combat/enemy-attack-type-trigger.mts b/src/triggers/combat/enemy-attack-type-trigger.mts index 7c9829b..0d49133 100644 --- a/src/triggers/combat/enemy-attack-type-trigger.mts +++ b/src/triggers/combat/enemy-attack-type-trigger.mts @@ -12,6 +12,7 @@ interface Data { const triggerCtx = defineLocalTrigger({ category: InternalCategory.COMBAT, check: d => game.combat.isActive && game.combat.enemy.attackType === d.type, + description: 'Fires when the monster has the given attack type', id: TriggerId.CombatEnemyAtkType, init() { ctx.patch(CombatManager, 'spawnEnemy').after(() => { diff --git a/src/triggers/combat/enemy-special-attack-trigger.tsx b/src/triggers/combat/enemy-special-attack-trigger.tsx index 5b4c2dc..eee4ff9 100644 --- a/src/triggers/combat/enemy-special-attack-trigger.tsx +++ b/src/triggers/combat/enemy-special-attack-trigger.tsx @@ -24,6 +24,7 @@ const triggerCtx = defineLocalTrigger({ {atk?.name} ), + description: 'Fires when the monster is casting this special attack', id: TriggerId.CombatSpecialAttack, init() { ctx.patch(Enemy, 'queueNextAction').after(() => { diff --git a/src/ui/components/trigger-config.tsx b/src/ui/components/trigger-config.tsx index ced1881..f872d8d 100644 --- a/src/ui/components/trigger-config.tsx +++ b/src/ui/components/trigger-config.tsx @@ -1,9 +1,8 @@ -import type {VNode} from 'preact'; +import {Fragment} from 'preact'; import {memo} from 'preact/compat'; import {useCallback} from 'preact/hooks'; import type {TriggerDefinitionContext} from '../../lib/data/trigger-definition-context.mjs'; import type {Obj} from '../../public_api'; -import Td from './td'; import {TriggerSelect} from './workflow-editor/categorised-node-select/categorised-node-select-impl'; import RenderNodeOption from './workflow-editor/render-node-option/render-node-option'; @@ -33,56 +32,44 @@ const TriggerConfig = memo( const {opts, trigger} = value; const triggerId = trigger?.id; - return ( -
- - - + const options = trigger?.def.options; - {trigger?.def.options?.map(opt => { - const show = opt.showIf?.(opts); + return ( + + + {options && ( +
+
+ + {options.map(opt => { + const show = opt.showIf?.(opts); - return show !== false && ( - { - const newOpts = { - ...opts, - [opt.id]: newVal, - }; - opt.resets?.forEach(prop => { - delete newOpts[prop]; - }); + return show !== false && ( + { + const newOpts = { + ...opts, + [opt.id]: newVal, + }; + opt.resets?.forEach(prop => { + delete newOpts[prop]; + }); - onChange({...value, opts: newOpts}); - }} - option={opt} - otherValues={opts} - value={opts[opt.id]}/> - ); - })} - -
-
+ onChange({...value, opts: newOpts}); + }} + option={opt} + otherValues={opts} + value={opts[opt.id]}/> + ); + })} + + + + )} + ); } ); export default TriggerConfig; - -interface TriggerProps { - value: TriggerConfigValue['trigger'] | undefined; - - onChange(val: TriggerConfigValue['trigger']): void; -} - -const Trigger = memo(function Trigger({value, onChange}): VNode { - return ( - - {'Trigger'} - - - - - ); -}); diff --git a/src/ui/components/workflow-editor/categorised-node-select/categorised-node-select.tsx b/src/ui/components/workflow-editor/categorised-node-select/categorised-node-select.tsx index d6685cd..63ee7d8 100644 --- a/src/ui/components/workflow-editor/categorised-node-select/categorised-node-select.tsx +++ b/src/ui/components/workflow-editor/categorised-node-select/categorised-node-select.tsx @@ -1,14 +1,16 @@ import type {ComponentChildren, VNode} from 'preact'; -import {useCallback} from 'preact/compat'; +import {memo, useCallback} from 'preact/compat'; import type {NamespacedDefinition} from '../../../../lib/namespaced-definition.mjs'; import type {StdRegistryKey} from '../../../../lib/registries/registries.mjs'; import type {CategorisedObject} from '../../../../lib/util/categorise-registry-objects.mjs'; import {errorLog} from '../../../../lib/util/log.mjs'; import type {Referenceable} from '../../../../public_api'; +import useTippy from '../../../hooks/tippy.mjs'; +import {mkClass} from '../../../util/mk-class.mjs'; import Btn from '../../btn'; import {BinSvg} from '../../svg'; -type Def = NamespacedDefinition; +type Def = NamespacedDefinition; interface Props { children?: ComponentChildren; @@ -26,6 +28,12 @@ interface Props { export {Props as CategorisedNodeSelectProps}; +export interface CategorisedNode extends Referenceable { + deprecated?: true | string; + + description?: string; +} + /** Common select for actions & triggers */ export default function CategorisedNodeSelect({ children, @@ -35,22 +43,36 @@ export default function CategorisedNodeSelect({ value, values, }: Props): VNode { - const selectedId = value?.id; const clear = useClear(value, onChange); const onSelectChange = useOnSelectChange(selectedId, onChange, registry, values); + const {description, deprecated} = value?.def ?? {} as Partial; + return ( -
+
{children} {clearable && } - {values.map(optGroupMapper)} + {description && } + {deprecated && ( + + )}
); } +const InfoBubble = memo<{clazz?: string; text: string;}>( + function InfoBubble({clazz, text}) { + const ref = useTippy(text); + + return ; + } +); + function useClear(value: T, onChange: () => void): () => void { const isClearable = value != null; diff --git a/src/ui/pages/help-page.tsx b/src/ui/pages/help-page.tsx index 485288a..d8a68f2 100644 --- a/src/ui/pages/help-page.tsx +++ b/src/ui/pages/help-page.tsx @@ -1,7 +1,7 @@ import type {VNode} from 'preact'; import PageContainer from '../components/page-container'; import autoId from '../util/id-gen.mjs'; -import {EmbeddedWorkflowsHelp, LoopsHelp, OfflineHelp, SettingUpHelp} from './help-page/help-pages'; +import {LoopsHelp, OfflineHelp, SettingUpHelp} from './help-page/help-pages'; export const HELP_PAGE_ID = autoId(); @@ -12,7 +12,6 @@ export default function HelpPage(): VNode { -
); diff --git a/src/ui/pages/help-page/help-pages.tsx b/src/ui/pages/help-page/help-pages.tsx index 0216ba0..786d25c 100644 --- a/src/ui/pages/help-page/help-pages.tsx +++ b/src/ui/pages/help-page/help-pages.tsx @@ -32,11 +32,3 @@ export function OfflineHelp(): VNode { ); } - -export function EmbeddedWorkflowsHelp(): VNode { - return ( - - {'They\'re for use with the "Run embedded workflow" action - they\'re essentially the same as normal workflows, but are only usable within the workflow they\'re defined in'} - - ); -}