mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
feat(wired-ui): add advanced condition editors
This commit is contained in:
+68
-8
@@ -13,12 +13,72 @@
|
||||
"widget.settings.interface.fps.warning": "Het zetten van FPS naar unlimited kan prestatie problemen veroorzaken!",
|
||||
"widget.settings.interface.secondary": "Verander de window header kleur",
|
||||
"widget.settings.interface.reset": "Reset header kleur naar default",
|
||||
"widget.room.chat.hide_pets": "Verberg dieren",
|
||||
"widget.room.chat.hide_avatars": "Verberg avatars",
|
||||
"widget.room.chat.hide_balloon": "Verberg Spreekballon",
|
||||
"widget.room.chat.show_balloon": "Spreekballon",
|
||||
"widget.room.chat.clear_history": "leeg geschiedenis",
|
||||
"widget.room.youtube.shared": "YouTube word gedeeld",
|
||||
"widget.room.youtube.open_video": "Open de video",
|
||||
"wiredfurni.params.area_selection.selected": "Geselecteerd gebied: Lengte=%x%, Breedte=%y%, breedte=%w%, hoogte=%h%"
|
||||
"widget.room.chat.hide_pets": "Verberg dieren",
|
||||
"widget.room.chat.hide_avatars": "Verberg avatars",
|
||||
"widget.room.chat.hide_balloon": "Verberg Spreekballon",
|
||||
"widget.room.chat.show_balloon": "Spreekballon",
|
||||
"widget.room.chat.clear_history": "leeg geschiedenis",
|
||||
"widget.room.youtube.shared": "YouTube word gedeeld",
|
||||
"widget.room.youtube.open_video": "Open de video",
|
||||
"wiredfurni.params.area_selection.selected": "Geselecteerd gebied: Lengte=%x%, Breedte=%y%, breedte=%w%, hoogte=%h%",
|
||||
"wiredfurni.params.sources.collapse": "Nascondi le impostazioni avanzate",
|
||||
"wiredfurni.params.sources.expand": "Mostra le impostazioni avanzate",
|
||||
"wiredfurni.params.quantifier_selection": "Abbina condizione se:",
|
||||
"wiredfurni.params.quantifier.users.0": "Tutti gli utenti corrispondono",
|
||||
"wiredfurni.params.quantifier.users.1": "Uno qualsiasi degli utenti corrisponde",
|
||||
"wiredfurni.params.quantifier.users.neg.0": "Uno qualsiasi degli utenti non corrisponde",
|
||||
"wiredfurni.params.quantifier.users.neg.1": "Nessuno degli utenti corrisponde",
|
||||
"wiredfurni.params.quantifier.furni.0": "Tutti i Furni corrispondono",
|
||||
"wiredfurni.params.quantifier.furni.1": "Uno qualsiasi dei Furni corrisponde",
|
||||
"wiredfurni.params.usertype.1": "Habbo",
|
||||
"wiredfurni.params.usertype.2": "Cucciolo",
|
||||
"wiredfurni.params.usertype.4": "Bot",
|
||||
"wiredfurni.params.sources.users.title.match.0": "Gli utenti da abbinare:",
|
||||
"wiredfurni.params.sources.users.title.match.1": "Utenti da comparare con:",
|
||||
"wiredfurni.params.sources.users.101": "Usa l'utente specificato dal nome",
|
||||
"wiredfurni.params.comparison.0": "Più basso di",
|
||||
"wiredfurni.params.comparison.1": "È uguale a",
|
||||
"wiredfurni.params.comparison.2": "Più alto di",
|
||||
"wiredfurni.params.team": "Scegli una squadra",
|
||||
"wiredfurni.params.team.1": "Rossa",
|
||||
"wiredfurni.params.team.2": "Verde",
|
||||
"wiredfurni.params.team.3": "Blu",
|
||||
"wiredfurni.params.team.4": "Gialla",
|
||||
"wiredfurni.params.team.triggerer": "Squadra dell'innescatore",
|
||||
"wiredfurni.params.comparison_selection": "Scegli tipo:",
|
||||
"wiredfurni.params.setscore2": "La squadra deve segnare:",
|
||||
"wiredfurni.params.placement_selection": "Posizione:",
|
||||
"wiredfurni.params.placement.1": "1.",
|
||||
"wiredfurni.params.placement.2": "2.",
|
||||
"wiredfurni.params.placement.3": "3.",
|
||||
"wiredfurni.params.placement.4": "4.",
|
||||
"wiredfurni.params.time.hour_selection": "Ore:",
|
||||
"wiredfurni.params.time.minute_selection": "Minuti:",
|
||||
"wiredfurni.params.time.second_selection": "Secondi:",
|
||||
"wiredfurni.params.time.skip": "Non usare il filtro",
|
||||
"wiredfurni.params.time.exact": "Esatto",
|
||||
"wiredfurni.params.time.range": "Range",
|
||||
"wiredfurni.params.time.weekday_selection": "Giorno della settimana:",
|
||||
"wiredfurni.params.time.weekday.1": "Lunedì",
|
||||
"wiredfurni.params.time.weekday.2": "Martedì",
|
||||
"wiredfurni.params.time.weekday.3": "Mercoledì",
|
||||
"wiredfurni.params.time.weekday.4": "Giovedì",
|
||||
"wiredfurni.params.time.weekday.5": "Venerdì",
|
||||
"wiredfurni.params.time.weekday.6": "Sabato",
|
||||
"wiredfurni.params.time.weekday.7": "Domenica",
|
||||
"wiredfurni.params.time.day_selection": "Giorno:",
|
||||
"wiredfurni.params.time.month_selection": "Mese:",
|
||||
"wiredfurni.params.time.month.10": "Ott.",
|
||||
"wiredfurni.params.time.month.11": "Nov.",
|
||||
"wiredfurni.params.time.month.12": "Dic.",
|
||||
"wiredfurni.params.time.month.1": "Gen.",
|
||||
"wiredfurni.params.time.month.2": "Feb.",
|
||||
"wiredfurni.params.time.month.3": "Mar.",
|
||||
"wiredfurni.params.time.month.4": "Apr.",
|
||||
"wiredfurni.params.time.month.5": "Mag.",
|
||||
"wiredfurni.params.time.month.6": "Giu.",
|
||||
"wiredfurni.params.time.month.7": "Lug.",
|
||||
"wiredfurni.params.time.month.8": "Ago.",
|
||||
"wiredfurni.params.time.month.9": "Set.",
|
||||
"wiredfurni.params.time.year_selection": "Anno:"
|
||||
}
|
||||
|
||||
@@ -27,4 +27,14 @@ export class WiredConditionlayout
|
||||
public static DATE_RANGE_ACTIVE: number = 24;
|
||||
public static ACTOR_HAS_HANDITEM: number = 25;
|
||||
public static COUNTER_TIME_MATCHES: number = 27;
|
||||
public static USER_PERFORMS_ACTION: number = 28;
|
||||
public static HAS_ALTITUDE: number = 29;
|
||||
public static NOT_USER_PERFORMS_ACTION: number = 30;
|
||||
public static NOT_ACTOR_HAS_HANDITEM: number = 31;
|
||||
public static TRIGGERER_MATCH: number = 32;
|
||||
public static NOT_TRIGGERER_MATCH: number = 33;
|
||||
public static TEAM_HAS_SCORE: number = 34;
|
||||
public static TEAM_HAS_RANK: number = 35;
|
||||
public static MATCH_TIME: number = 36;
|
||||
public static MATCH_DATE: number = 37;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Slider, Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||
import { WiredConditionBaseView } from './WiredConditionBaseView';
|
||||
|
||||
const COUNTER_INTERACTION_TYPES = [ 'game_upcounter' ];
|
||||
const MIN_ALTITUDE = 0;
|
||||
const MAX_ALTITUDE = 40;
|
||||
const ALTITUDE_STEP = 0.01;
|
||||
const ALTITUDE_PATTERN = /^\d*(\.\d{0,2})?$/;
|
||||
|
||||
const clampAltitude = (value: number) =>
|
||||
{
|
||||
if(isNaN(value)) return MIN_ALTITUDE;
|
||||
|
||||
const clamped = Math.min(MAX_ALTITUDE, Math.max(MIN_ALTITUDE, value));
|
||||
|
||||
return parseFloat(clamped.toFixed(2));
|
||||
};
|
||||
|
||||
const formatAltitude = (value: number) =>
|
||||
{
|
||||
const normalized = clampAltitude(value);
|
||||
const text = normalized.toFixed(2);
|
||||
|
||||
return text.replace(/\.00$/, '').replace(/(\.\d)0$/, '$1');
|
||||
};
|
||||
|
||||
const parseAltitude = (value: string) =>
|
||||
{
|
||||
if(!value || !value.trim().length) return 0;
|
||||
|
||||
const parsed = parseFloat(value);
|
||||
|
||||
if(isNaN(parsed)) return 0;
|
||||
|
||||
return clampAltitude(parsed);
|
||||
};
|
||||
|
||||
export const WiredConditionHasAltitudeView: FC<{}> = () =>
|
||||
{
|
||||
const { trigger = null, setIntParams = null, setStringParam = null, setAllowedInteractionTypes = null, setAllowedInteractionErrorKey = null } = useWired();
|
||||
const [ comparison, setComparison ] = useState(1);
|
||||
const [ furniSource, setFurniSource ] = useState<number>(() =>
|
||||
{
|
||||
if(trigger?.intData?.length > 1) return trigger.intData[1];
|
||||
return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0;
|
||||
});
|
||||
const [ quantifier, setQuantifier ] = useState(0);
|
||||
const [ showAdvanced, setShowAdvanced ] = useState(false);
|
||||
const [ altitude, setAltitude ] = useState(0);
|
||||
const [ altitudeInput, setAltitudeInput ] = useState('0');
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setAllowedInteractionTypes(COUNTER_INTERACTION_TYPES);
|
||||
setAllowedInteractionErrorKey('wiredfurni.error.require_counter_furni');
|
||||
|
||||
return () =>
|
||||
{
|
||||
setAllowedInteractionTypes(null);
|
||||
setAllowedInteractionErrorKey(null);
|
||||
};
|
||||
}, [ setAllowedInteractionErrorKey, setAllowedInteractionTypes ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
|
||||
setComparison((trigger.intData.length > 0) ? trigger.intData[0] : 1);
|
||||
setFurniSource((trigger.intData.length > 1) ? trigger.intData[1] : ((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0));
|
||||
setQuantifier((trigger.intData.length > 2) ? trigger.intData[2] : 0);
|
||||
setShowAdvanced((trigger.intData.length > 1) ? (trigger.intData[1] !== 0 || trigger.intData[2] !== 0) : false);
|
||||
|
||||
const nextAltitude = parseAltitude(trigger.stringData);
|
||||
setAltitude(nextAltitude);
|
||||
setAltitudeInput(formatAltitude(nextAltitude));
|
||||
}, [ trigger ]);
|
||||
|
||||
const updateAltitude = (value: number) =>
|
||||
{
|
||||
const nextValue = clampAltitude(value);
|
||||
|
||||
setAltitude(nextValue);
|
||||
setAltitudeInput(formatAltitude(nextValue));
|
||||
};
|
||||
|
||||
const updateAltitudeInput = (value: string) =>
|
||||
{
|
||||
if(!ALTITUDE_PATTERN.test(value)) return;
|
||||
|
||||
setAltitudeInput(value);
|
||||
|
||||
if(!value.length)
|
||||
{
|
||||
setAltitude(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedValue = parseFloat(value);
|
||||
|
||||
if(isNaN(parsedValue)) return;
|
||||
|
||||
if(parsedValue > MAX_ALTITUDE)
|
||||
{
|
||||
updateAltitude(MAX_ALTITUDE);
|
||||
return;
|
||||
}
|
||||
|
||||
setAltitude(clampAltitude(parsedValue));
|
||||
};
|
||||
|
||||
const save = () =>
|
||||
{
|
||||
setIntParams([
|
||||
comparison,
|
||||
furniSource,
|
||||
quantifier
|
||||
]);
|
||||
setStringParam(formatAltitude(altitude));
|
||||
};
|
||||
|
||||
return (
|
||||
<WiredConditionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
|
||||
save={ save }
|
||||
footer={
|
||||
<div className="flex flex-col gap-2">
|
||||
<button className="btn btn-link p-0 align-self-start" type="button" onClick={ () => setShowAdvanced(value => !value) }>
|
||||
{ LocalizeText(showAdvanced ? 'wiredfurni.params.sources.collapse' : 'wiredfurni.params.sources.expand') }
|
||||
</button>
|
||||
{ showAdvanced &&
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
|
||||
{ [ 0, 1 ].map(value =>
|
||||
{
|
||||
return (
|
||||
<div key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" id={ `altitudeQuantifier${ value }` } name="altitudeQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.quantifier.furni.${ value }`) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ setFurniSource } />
|
||||
</> }
|
||||
</div>
|
||||
}>
|
||||
<div className="flex flex-col gap-2">
|
||||
{ [ 0, 1, 2 ].map(value =>
|
||||
{
|
||||
return (
|
||||
<div key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (comparison === value) } className="form-check-input" id={ `altitudeComparison${ value }` } name="altitudeComparison" type="radio" onChange={ () => setComparison(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.comparison.${ value }`) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.setaltitude') }</Text>
|
||||
<input
|
||||
className="form-control form-control-sm"
|
||||
inputMode="decimal"
|
||||
type="text"
|
||||
value={ altitudeInput }
|
||||
onBlur={ () => setAltitudeInput(formatAltitude(altitude)) }
|
||||
onChange={ event => updateAltitudeInput(event.target.value) } />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Slider
|
||||
max={ MAX_ALTITUDE }
|
||||
min={ MIN_ALTITUDE }
|
||||
step={ ALTITUDE_STEP }
|
||||
value={ altitude }
|
||||
onChange={ event => updateAltitude(event as number) } />
|
||||
<Text small>{ formatAltitude(altitude) }</Text>
|
||||
</div>
|
||||
</WiredConditionBaseView>
|
||||
);
|
||||
};
|
||||
@@ -7,6 +7,9 @@ import { WiredConditionActorIsWearingBadgeView } from './WiredConditionActorIsWe
|
||||
import { WiredConditionActorIsWearingEffectView } from './WiredConditionActorIsWearingEffectView';
|
||||
import { WiredConditionCounterTimeMatchesView } from './WiredConditionCounterTimeMatchesView';
|
||||
import { WiredConditionDateRangeView } from './WiredConditionDateRangeView';
|
||||
import { WiredConditionMatchDateView } from './WiredConditionMatchDateView';
|
||||
import { WiredConditionMatchTimeView } from './WiredConditionMatchTimeView';
|
||||
import { WiredConditionHasAltitudeView } from './WiredConditionHasAltitudeView';
|
||||
import { WiredConditionFurniHasAvatarOnView } from './WiredConditionFurniHasAvatarOnView';
|
||||
import { WiredConditionFurniHasFurniOnView } from './WiredConditionFurniHasFurniOnView';
|
||||
import { WiredConditionFurniHasNotFurniOnView } from './WiredConditionFurniHasNotFurniOnView';
|
||||
@@ -14,6 +17,10 @@ import { WiredConditionFurniIsOfTypeView } from './WiredConditionFurniIsOfTypeVi
|
||||
import { WiredConditionFurniMatchesSnapshotView } from './WiredConditionFurniMatchesSnapshotView';
|
||||
import { WiredConditionTimeElapsedLessView } from './WiredConditionTimeElapsedLessView';
|
||||
import { WiredConditionTimeElapsedMoreView } from './WiredConditionTimeElapsedMoreView';
|
||||
import { WiredConditionTeamHasRankView } from './WiredConditionTeamHasRankView';
|
||||
import { WiredConditionTeamHasScoreView } from './WiredConditionTeamHasScoreView';
|
||||
import { WiredConditionTriggererMatchView } from './WiredConditionTriggererMatchView';
|
||||
import { WiredConditionUserPerformsActionView } from './WiredConditionUserPerformsActionView';
|
||||
import { WiredConditionUserCountInRoomView } from './WiredConditionUserCountInRoomView';
|
||||
|
||||
export const WiredConditionLayoutView = (code: number) =>
|
||||
@@ -21,7 +28,11 @@ export const WiredConditionLayoutView = (code: number) =>
|
||||
switch(code)
|
||||
{
|
||||
case WiredConditionlayout.ACTOR_HAS_HANDITEM:
|
||||
case WiredConditionlayout.NOT_ACTOR_HAS_HANDITEM:
|
||||
return <WiredConditionActorHasHandItemView />;
|
||||
case WiredConditionlayout.TRIGGERER_MATCH:
|
||||
case WiredConditionlayout.NOT_TRIGGERER_MATCH:
|
||||
return <WiredConditionTriggererMatchView />;
|
||||
case WiredConditionlayout.ACTOR_IS_GROUP_MEMBER:
|
||||
case WiredConditionlayout.NOT_ACTOR_IN_GROUP:
|
||||
return <WiredConditionActorIsGroupMemberView />;
|
||||
@@ -39,6 +50,10 @@ export const WiredConditionLayoutView = (code: number) =>
|
||||
return <WiredConditionActorIsWearingEffectView />;
|
||||
case WiredConditionlayout.DATE_RANGE_ACTIVE:
|
||||
return <WiredConditionDateRangeView />;
|
||||
case WiredConditionlayout.MATCH_TIME:
|
||||
return <WiredConditionMatchTimeView />;
|
||||
case WiredConditionlayout.MATCH_DATE:
|
||||
return <WiredConditionMatchDateView />;
|
||||
case WiredConditionlayout.FURNIS_HAVE_AVATARS:
|
||||
case WiredConditionlayout.FURNI_NOT_HAVE_HABBO:
|
||||
return <WiredConditionFurniHasAvatarOnView />;
|
||||
@@ -61,6 +76,16 @@ export const WiredConditionLayoutView = (code: number) =>
|
||||
return <WiredConditionUserCountInRoomView />;
|
||||
case WiredConditionlayout.COUNTER_TIME_MATCHES:
|
||||
return <WiredConditionCounterTimeMatchesView />;
|
||||
case WiredConditionlayout.USER_PERFORMS_ACTION:
|
||||
return <WiredConditionUserPerformsActionView />;
|
||||
case WiredConditionlayout.NOT_USER_PERFORMS_ACTION:
|
||||
return <WiredConditionUserPerformsActionView negative={ true } />;
|
||||
case WiredConditionlayout.HAS_ALTITUDE:
|
||||
return <WiredConditionHasAltitudeView />;
|
||||
case WiredConditionlayout.TEAM_HAS_SCORE:
|
||||
return <WiredConditionTeamHasScoreView />;
|
||||
case WiredConditionlayout.TEAM_HAS_RANK:
|
||||
return <WiredConditionTeamHasRankView />;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
import { ChangeEvent, FC, useEffect, useMemo, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredConditionBaseView } from './WiredConditionBaseView';
|
||||
|
||||
const MODE_SKIP = 0;
|
||||
const MODE_EXACT = 1;
|
||||
const MODE_RANGE = 2;
|
||||
const MODE_OPTIONS = [ MODE_SKIP, MODE_EXACT, MODE_RANGE ];
|
||||
const WEEKDAY_OPTIONS = [ 1, 2, 3, 4, 5, 6, 7 ];
|
||||
const MONTH_OPTIONS = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ];
|
||||
|
||||
const createMask = (values: number[]) => values.reduce((mask, value) => (mask | (1 << value)), 0);
|
||||
const ALL_WEEKDAYS_MASK = createMask(WEEKDAY_OPTIONS);
|
||||
const ALL_MONTHS_MASK = createMask(MONTH_OPTIONS);
|
||||
|
||||
const clampValue = (value: number, min: number, max: number) =>
|
||||
{
|
||||
if(isNaN(value)) return min;
|
||||
|
||||
return Math.max(min, Math.min(max, Math.floor(value)));
|
||||
};
|
||||
|
||||
const parseInputValue = (event: ChangeEvent<HTMLInputElement>, min: number, max: number) =>
|
||||
{
|
||||
return clampValue(parseInt(event.target.value || min.toString(), 10), min, max);
|
||||
};
|
||||
|
||||
const toggleMaskValue = (mask: number, value: number, enabled: boolean) =>
|
||||
{
|
||||
if(enabled) return (mask | (1 << value));
|
||||
|
||||
return (mask & ~(1 << value));
|
||||
};
|
||||
|
||||
const InlineNumberInput: FC<{ value: number; min: number; max: number; onChange: (value: number) => void }> = props =>
|
||||
{
|
||||
const { value = 0, min = 0, max = 0, onChange = null } = props;
|
||||
|
||||
return (
|
||||
<input
|
||||
className="form-control form-control-sm text-center"
|
||||
max={ max }
|
||||
min={ min }
|
||||
style={ { width: 72 } }
|
||||
type="number"
|
||||
value={ value }
|
||||
onChange={ event => onChange(parseInputValue(event, min, max)) } />
|
||||
);
|
||||
};
|
||||
|
||||
interface MatchDateSectionProps
|
||||
{
|
||||
sectionId: string;
|
||||
titleKey: string;
|
||||
mode: number;
|
||||
fromValue: number;
|
||||
toValue: number;
|
||||
min: number;
|
||||
max: number;
|
||||
onModeChange: (value: number) => void;
|
||||
onFromChange: (value: number) => void;
|
||||
onToChange: (value: number) => void;
|
||||
}
|
||||
|
||||
const MatchDateSection: FC<MatchDateSectionProps> = props =>
|
||||
{
|
||||
const { sectionId = '', titleKey = '', mode = MODE_SKIP, fromValue = 0, toValue = 0, min = 0, max = 0, onModeChange = null, onFromChange = null, onToChange = null } = props;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text bold>{ LocalizeText(titleKey) }</Text>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ (mode === MODE_SKIP) } className="form-check-input" id={ `${ sectionId }0` } name={ sectionId } type="radio" onChange={ () => onModeChange(MODE_SKIP) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.time.skip') }</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<input checked={ (mode === MODE_EXACT) } className="form-check-input" id={ `${ sectionId }1` } name={ sectionId } type="radio" onChange={ () => onModeChange(MODE_EXACT) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.time.exact') }</Text>
|
||||
<InlineNumberInput max={ max } min={ min } value={ fromValue } onChange={ onFromChange } />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<input checked={ (mode === MODE_RANGE) } className="form-check-input" id={ `${ sectionId }2` } name={ sectionId } type="radio" onChange={ () => onModeChange(MODE_RANGE) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.time.range') }</Text>
|
||||
<InlineNumberInput max={ max } min={ min } value={ fromValue } onChange={ onFromChange } />
|
||||
<Text>-</Text>
|
||||
<InlineNumberInput max={ max } min={ min } value={ toValue } onChange={ onToChange } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const WiredConditionMatchDateView: FC<{}> = () =>
|
||||
{
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
const currentYear = useMemo(() => new Date().getFullYear(), []);
|
||||
const [ weekdayMask, setWeekdayMask ] = useState(ALL_WEEKDAYS_MASK);
|
||||
const [ dayMode, setDayMode ] = useState(MODE_SKIP);
|
||||
const [ dayFrom, setDayFrom ] = useState(1);
|
||||
const [ dayTo, setDayTo ] = useState(31);
|
||||
const [ monthMask, setMonthMask ] = useState(ALL_MONTHS_MASK);
|
||||
const [ yearMode, setYearMode ] = useState(MODE_SKIP);
|
||||
const [ yearFrom, setYearFrom ] = useState(currentYear);
|
||||
const [ yearTo, setYearTo ] = useState(currentYear);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
|
||||
setWeekdayMask((trigger.intData[0] && (trigger.intData[0] > 0)) ? trigger.intData[0] : ALL_WEEKDAYS_MASK);
|
||||
setDayMode(MODE_OPTIONS.includes(trigger.intData[1]) ? trigger.intData[1] : MODE_SKIP);
|
||||
setDayFrom(clampValue(trigger.intData[2] ?? 1, 1, 31));
|
||||
setDayTo(clampValue(trigger.intData[3] ?? 31, 1, 31));
|
||||
setMonthMask((trigger.intData[4] && (trigger.intData[4] > 0)) ? trigger.intData[4] : ALL_MONTHS_MASK);
|
||||
setYearMode(MODE_OPTIONS.includes(trigger.intData[5]) ? trigger.intData[5] : MODE_SKIP);
|
||||
setYearFrom(clampValue(trigger.intData[6] ?? currentYear, 1, 9999));
|
||||
setYearTo(clampValue(trigger.intData[7] ?? currentYear, 1, 9999));
|
||||
}, [ currentYear, trigger ]);
|
||||
|
||||
const save = () =>
|
||||
{
|
||||
setIntParams([
|
||||
weekdayMask || ALL_WEEKDAYS_MASK,
|
||||
dayMode,
|
||||
clampValue(dayFrom, 1, 31),
|
||||
clampValue(dayTo, 1, 31),
|
||||
monthMask || ALL_MONTHS_MASK,
|
||||
yearMode,
|
||||
clampValue(yearFrom, 1, 9999),
|
||||
clampValue(yearTo, 1, 9999)
|
||||
]);
|
||||
};
|
||||
|
||||
return (
|
||||
<WiredConditionBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save }>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.time.weekday_selection') }</Text>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{ WEEKDAY_OPTIONS.map(value =>
|
||||
{
|
||||
const checked = ((weekdayMask & (1 << value)) !== 0);
|
||||
|
||||
return (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ checked } className="form-check-input" type="checkbox" onChange={ event => setWeekdayMask(toggleMaskValue(weekdayMask, value, event.target.checked)) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.time.weekday.${ value }`) }</Text>
|
||||
</label>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
</div>
|
||||
<MatchDateSection
|
||||
fromValue={ dayFrom }
|
||||
max={ 31 }
|
||||
min={ 1 }
|
||||
mode={ dayMode }
|
||||
sectionId="matchDateDay"
|
||||
titleKey="wiredfurni.params.time.day_selection"
|
||||
toValue={ dayTo }
|
||||
onFromChange={ value => setDayFrom(clampValue(value, 1, 31)) }
|
||||
onModeChange={ setDayMode }
|
||||
onToChange={ value => setDayTo(clampValue(value, 1, 31)) } />
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.time.month_selection') }</Text>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{ MONTH_OPTIONS.map(value =>
|
||||
{
|
||||
const checked = ((monthMask & (1 << value)) !== 0);
|
||||
|
||||
return (
|
||||
<label key={ value } className="flex items-center gap-1">
|
||||
<input checked={ checked } className="form-check-input" type="checkbox" onChange={ event => setMonthMask(toggleMaskValue(monthMask, value, event.target.checked)) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.time.month.${ value }`) }</Text>
|
||||
</label>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
</div>
|
||||
<MatchDateSection
|
||||
fromValue={ yearFrom }
|
||||
max={ 9999 }
|
||||
min={ 1 }
|
||||
mode={ yearMode }
|
||||
sectionId="matchDateYear"
|
||||
titleKey="wiredfurni.params.time.year_selection"
|
||||
toValue={ yearTo }
|
||||
onFromChange={ value => setYearFrom(clampValue(value, 1, 9999)) }
|
||||
onModeChange={ setYearMode }
|
||||
onToChange={ value => setYearTo(clampValue(value, 1, 9999)) } />
|
||||
</div>
|
||||
</WiredConditionBaseView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,163 @@
|
||||
import { ChangeEvent, FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredConditionBaseView } from './WiredConditionBaseView';
|
||||
|
||||
const MODE_SKIP = 0;
|
||||
const MODE_EXACT = 1;
|
||||
const MODE_RANGE = 2;
|
||||
const MODE_OPTIONS = [ MODE_SKIP, MODE_EXACT, MODE_RANGE ];
|
||||
|
||||
const clampValue = (value: number, min: number, max: number) =>
|
||||
{
|
||||
if(isNaN(value)) return min;
|
||||
|
||||
return Math.max(min, Math.min(max, Math.floor(value)));
|
||||
};
|
||||
|
||||
interface TimeFilterSectionProps
|
||||
{
|
||||
sectionId: string;
|
||||
titleKey: string;
|
||||
min: number;
|
||||
max: number;
|
||||
mode: number;
|
||||
fromValue: number;
|
||||
toValue: number;
|
||||
onModeChange: (value: number) => void;
|
||||
onFromChange: (value: number) => void;
|
||||
onToChange: (value: number) => void;
|
||||
}
|
||||
|
||||
const parseInputValue = (event: ChangeEvent<HTMLInputElement>, min: number, max: number) =>
|
||||
{
|
||||
return clampValue(parseInt(event.target.value || min.toString(), 10), min, max);
|
||||
};
|
||||
|
||||
const InlineNumberInput: FC<{ value: number; min: number; max: number; onChange: (value: number) => void }> = props =>
|
||||
{
|
||||
const { value = 0, min = 0, max = 0, onChange = null } = props;
|
||||
|
||||
return (
|
||||
<input
|
||||
className="form-control form-control-sm text-center"
|
||||
max={ max }
|
||||
min={ min }
|
||||
style={ { width: 56 } }
|
||||
type="number"
|
||||
value={ value }
|
||||
onChange={ event => onChange(parseInputValue(event, min, max)) } />
|
||||
);
|
||||
};
|
||||
|
||||
const TimeFilterSection: FC<TimeFilterSectionProps> = props =>
|
||||
{
|
||||
const { sectionId = '', titleKey = '', min = 0, max = 0, mode = MODE_SKIP, fromValue = 0, toValue = 0, onModeChange = null, onFromChange = null, onToChange = null } = props;
|
||||
|
||||
return (
|
||||
<div className="d-flex flex-column gap-2">
|
||||
<Text bold>{ LocalizeText(titleKey) }</Text>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ (mode === MODE_SKIP) } className="form-check-input" id={ `${ sectionId }0` } name={ sectionId } type="radio" onChange={ () => onModeChange(MODE_SKIP) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.time.skip') }</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<input checked={ (mode === MODE_EXACT) } className="form-check-input" id={ `${ sectionId }1` } name={ sectionId } type="radio" onChange={ () => onModeChange(MODE_EXACT) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.time.exact') }</Text>
|
||||
<InlineNumberInput max={ max } min={ min } value={ fromValue } onChange={ onFromChange } />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<input checked={ (mode === MODE_RANGE) } className="form-check-input" id={ `${ sectionId }2` } name={ sectionId } type="radio" onChange={ () => onModeChange(MODE_RANGE) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.time.range') }</Text>
|
||||
<InlineNumberInput max={ max } min={ min } value={ fromValue } onChange={ onFromChange } />
|
||||
<Text>-</Text>
|
||||
<InlineNumberInput max={ max } min={ min } value={ toValue } onChange={ onToChange } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const WiredConditionMatchTimeView: FC<{}> = () =>
|
||||
{
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
const [ hourMode, setHourMode ] = useState(MODE_SKIP);
|
||||
const [ hourFrom, setHourFrom ] = useState(0);
|
||||
const [ hourTo, setHourTo ] = useState(0);
|
||||
const [ minuteMode, setMinuteMode ] = useState(MODE_SKIP);
|
||||
const [ minuteFrom, setMinuteFrom ] = useState(0);
|
||||
const [ minuteTo, setMinuteTo ] = useState(0);
|
||||
const [ secondMode, setSecondMode ] = useState(MODE_SKIP);
|
||||
const [ secondFrom, setSecondFrom ] = useState(0);
|
||||
const [ secondTo, setSecondTo ] = useState(0);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
|
||||
setHourMode(MODE_OPTIONS.includes(trigger.intData[0]) ? trigger.intData[0] : MODE_SKIP);
|
||||
setHourFrom(clampValue(trigger.intData[1] ?? 0, 0, 23));
|
||||
setHourTo(clampValue(trigger.intData[2] ?? 0, 0, 23));
|
||||
setMinuteMode(MODE_OPTIONS.includes(trigger.intData[3]) ? trigger.intData[3] : MODE_SKIP);
|
||||
setMinuteFrom(clampValue(trigger.intData[4] ?? 0, 0, 59));
|
||||
setMinuteTo(clampValue(trigger.intData[5] ?? 0, 0, 59));
|
||||
setSecondMode(MODE_OPTIONS.includes(trigger.intData[6]) ? trigger.intData[6] : MODE_SKIP);
|
||||
setSecondFrom(clampValue(trigger.intData[7] ?? 0, 0, 59));
|
||||
setSecondTo(clampValue(trigger.intData[8] ?? 0, 0, 59));
|
||||
}, [ trigger ]);
|
||||
|
||||
const save = () =>
|
||||
{
|
||||
setIntParams([
|
||||
hourMode,
|
||||
clampValue(hourFrom, 0, 23),
|
||||
clampValue(hourTo, 0, 23),
|
||||
minuteMode,
|
||||
clampValue(minuteFrom, 0, 59),
|
||||
clampValue(minuteTo, 0, 59),
|
||||
secondMode,
|
||||
clampValue(secondFrom, 0, 59),
|
||||
clampValue(secondTo, 0, 59)
|
||||
]);
|
||||
};
|
||||
|
||||
return (
|
||||
<WiredConditionBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save }>
|
||||
<div className="flex flex-col gap-3">
|
||||
<TimeFilterSection
|
||||
fromValue={ hourFrom }
|
||||
max={ 23 }
|
||||
min={ 0 }
|
||||
mode={ hourMode }
|
||||
sectionId="matchTimeHour"
|
||||
titleKey="wiredfurni.params.time.hour_selection"
|
||||
toValue={ hourTo }
|
||||
onFromChange={ value => setHourFrom(clampValue(value, 0, 23)) }
|
||||
onModeChange={ setHourMode }
|
||||
onToChange={ value => setHourTo(clampValue(value, 0, 23)) } />
|
||||
<TimeFilterSection
|
||||
fromValue={ minuteFrom }
|
||||
max={ 59 }
|
||||
min={ 0 }
|
||||
mode={ minuteMode }
|
||||
sectionId="matchTimeMinute"
|
||||
titleKey="wiredfurni.params.time.minute_selection"
|
||||
toValue={ minuteTo }
|
||||
onFromChange={ value => setMinuteFrom(clampValue(value, 0, 59)) }
|
||||
onModeChange={ setMinuteMode }
|
||||
onToChange={ value => setMinuteTo(clampValue(value, 0, 59)) } />
|
||||
<TimeFilterSection
|
||||
fromValue={ secondFrom }
|
||||
max={ 59 }
|
||||
min={ 0 }
|
||||
mode={ secondMode }
|
||||
sectionId="matchTimeSecond"
|
||||
titleKey="wiredfurni.params.time.second_selection"
|
||||
toValue={ secondTo }
|
||||
onFromChange={ value => setSecondFrom(clampValue(value, 0, 59)) }
|
||||
onModeChange={ setSecondMode }
|
||||
onToChange={ value => setSecondTo(clampValue(value, 0, 59)) } />
|
||||
</div>
|
||||
</WiredConditionBaseView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||
import { WiredConditionBaseView } from './WiredConditionBaseView';
|
||||
|
||||
const TEAM_OPTIONS = [ 0, 1, 2, 3, 4 ];
|
||||
const PLACEMENT_OPTIONS = [ 1, 2, 3, 4 ];
|
||||
|
||||
export const WiredConditionTeamHasRankView: FC<{}> = () =>
|
||||
{
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
const [ team, setTeam ] = useState(1);
|
||||
const [ placement, setPlacement ] = useState(1);
|
||||
const [ userSource, setUserSource ] = useState(0);
|
||||
const [ quantifier, setQuantifier ] = useState(0);
|
||||
const [ showAdvanced, setShowAdvanced ] = useState(false);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
|
||||
const nextTeam = (trigger.intData.length > 0) ? trigger.intData[0] : 1;
|
||||
const nextPlacement = (trigger.intData.length > 1) ? trigger.intData[1] : 1;
|
||||
const nextUserSource = (trigger.intData.length > 2) ? trigger.intData[2] : 0;
|
||||
const nextQuantifier = (trigger.intData.length > 3) ? trigger.intData[3] : 0;
|
||||
|
||||
setTeam(TEAM_OPTIONS.includes(nextTeam) ? nextTeam : 1);
|
||||
setPlacement(PLACEMENT_OPTIONS.includes(nextPlacement) ? nextPlacement : 1);
|
||||
setUserSource(nextUserSource);
|
||||
setQuantifier((nextQuantifier === 1) ? 1 : 0);
|
||||
setShowAdvanced(nextUserSource !== 0 || nextQuantifier !== 0);
|
||||
}, [ trigger ]);
|
||||
|
||||
const save = () =>
|
||||
{
|
||||
setIntParams([
|
||||
team,
|
||||
placement,
|
||||
userSource,
|
||||
quantifier
|
||||
]);
|
||||
};
|
||||
|
||||
return (
|
||||
<WiredConditionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE }
|
||||
save={ save }
|
||||
footer={
|
||||
<div className="flex flex-col gap-2">
|
||||
<button className="btn btn-link p-0 align-self-start" type="button" onClick={ () => setShowAdvanced(value => !value) }>
|
||||
{ LocalizeText(showAdvanced ? 'wiredfurni.params.sources.collapse' : 'wiredfurni.params.sources.expand') }
|
||||
</button>
|
||||
{ showAdvanced &&
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
|
||||
{ [ 0, 1 ].map(value =>
|
||||
{
|
||||
return (
|
||||
<div key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" id={ `teamRankQuantifier${ value }` } name="teamRankQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.quantifier.users.${ value }`) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } />
|
||||
</> }
|
||||
</div>
|
||||
}>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.team') }</Text>
|
||||
{ TEAM_OPTIONS.map(value =>
|
||||
{
|
||||
const labelKey = (value === 0) ? 'wiredfurni.params.team.triggerer' : `wiredfurni.params.team.${ value }`;
|
||||
|
||||
return (
|
||||
<div key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (team === value) } className="form-check-input" id={ `teamHasRank${ value }` } name="teamHasRank" type="radio" onChange={ () => setTeam(value) } />
|
||||
<Text>{ LocalizeText(labelKey) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.placement_selection') }</Text>
|
||||
{ PLACEMENT_OPTIONS.map(value =>
|
||||
{
|
||||
return (
|
||||
<div key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (placement === value) } className="form-check-input" id={ `teamRankPlacement${ value }` } name="teamRankPlacement" type="radio" onChange={ () => setPlacement(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.placement.${ value }`) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
</WiredConditionBaseView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,158 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Slider, Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||
import { WiredConditionBaseView } from './WiredConditionBaseView';
|
||||
|
||||
const TEAM_OPTIONS = [ 1, 2, 3, 4 ];
|
||||
const COMPARISON_OPTIONS = [ 0, 1, 2 ];
|
||||
const MIN_SCORE = 0;
|
||||
const MAX_SCORE = 999;
|
||||
const SCORE_PATTERN = /^\d*$/;
|
||||
|
||||
const clampScore = (value: number) =>
|
||||
{
|
||||
if(isNaN(value)) return MIN_SCORE;
|
||||
|
||||
return Math.max(MIN_SCORE, Math.min(MAX_SCORE, Math.floor(value)));
|
||||
};
|
||||
|
||||
export const WiredConditionTeamHasScoreView: FC<{}> = () =>
|
||||
{
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
const [ team, setTeam ] = useState(1);
|
||||
const [ comparison, setComparison ] = useState(1);
|
||||
const [ score, setScore ] = useState(0);
|
||||
const [ scoreInput, setScoreInput ] = useState('0');
|
||||
const [ userSource, setUserSource ] = useState(0);
|
||||
const [ quantifier, setQuantifier ] = useState(0);
|
||||
const [ showAdvanced, setShowAdvanced ] = useState(false);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
|
||||
const nextTeam = (trigger.intData.length > 0) ? trigger.intData[0] : 1;
|
||||
const nextComparison = (trigger.intData.length > 1) ? trigger.intData[1] : 1;
|
||||
const nextScore = clampScore((trigger.intData.length > 2) ? trigger.intData[2] : 0);
|
||||
const nextUserSource = (trigger.intData.length > 3) ? trigger.intData[3] : 0;
|
||||
const nextQuantifier = (trigger.intData.length > 4) ? trigger.intData[4] : 0;
|
||||
|
||||
setTeam(TEAM_OPTIONS.includes(nextTeam) ? nextTeam : 1);
|
||||
setComparison(COMPARISON_OPTIONS.includes(nextComparison) ? nextComparison : 1);
|
||||
setScore(nextScore);
|
||||
setScoreInput(nextScore.toString());
|
||||
setUserSource(nextUserSource);
|
||||
setQuantifier((nextQuantifier === 1) ? 1 : 0);
|
||||
setShowAdvanced(nextUserSource !== 0 || nextQuantifier !== 0);
|
||||
}, [ trigger ]);
|
||||
|
||||
const updateScore = (value: number) =>
|
||||
{
|
||||
const nextValue = clampScore(value);
|
||||
|
||||
setScore(nextValue);
|
||||
setScoreInput(nextValue.toString());
|
||||
};
|
||||
|
||||
const updateScoreInput = (value: string) =>
|
||||
{
|
||||
if(!SCORE_PATTERN.test(value)) return;
|
||||
|
||||
setScoreInput(value);
|
||||
|
||||
if(!value.length)
|
||||
{
|
||||
setScore(0);
|
||||
return;
|
||||
}
|
||||
|
||||
updateScore(parseInt(value));
|
||||
};
|
||||
|
||||
const save = () =>
|
||||
{
|
||||
setIntParams([
|
||||
team,
|
||||
comparison,
|
||||
clampScore(score),
|
||||
userSource,
|
||||
quantifier
|
||||
]);
|
||||
};
|
||||
|
||||
return (
|
||||
<WiredConditionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE }
|
||||
save={ save }
|
||||
footer={
|
||||
<div className="flex flex-col gap-2">
|
||||
<button className="btn btn-link p-0 align-self-start" type="button" onClick={ () => setShowAdvanced(value => !value) }>
|
||||
{ LocalizeText(showAdvanced ? 'wiredfurni.params.sources.collapse' : 'wiredfurni.params.sources.expand') }
|
||||
</button>
|
||||
{ showAdvanced &&
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
|
||||
{ [ 0, 1 ].map(value =>
|
||||
{
|
||||
return (
|
||||
<div key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" id={ `teamScoreQuantifier${ value }` } name="teamScoreQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.quantifier.users.${ value }`) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<WiredSourcesSelector showUsers={ true } userSource={ userSource } onChangeUsers={ setUserSource } />
|
||||
</> }
|
||||
</div>
|
||||
}>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.team') }</Text>
|
||||
{ TEAM_OPTIONS.map(value =>
|
||||
{
|
||||
return (
|
||||
<div key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (team === value) } className="form-check-input" id={ `teamHasScore${ value }` } name="teamHasScore" type="radio" onChange={ () => setTeam(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.team.${ value }`) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.comparison_selection') }</Text>
|
||||
{ COMPARISON_OPTIONS.map(value =>
|
||||
{
|
||||
return (
|
||||
<div key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (comparison === value) } className="form-check-input" id={ `teamScoreComparison${ value }` } name="teamScoreComparison" type="radio" onChange={ () => setComparison(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.comparison.${ value }`) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.setscore2') }</Text>
|
||||
<input
|
||||
className="form-control form-control-sm"
|
||||
inputMode="numeric"
|
||||
type="text"
|
||||
value={ scoreInput }
|
||||
onBlur={ () => setScoreInput(clampScore(score).toString()) }
|
||||
onChange={ event => updateScoreInput(event.target.value) } />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Slider
|
||||
max={ MAX_SCORE }
|
||||
min={ MIN_SCORE }
|
||||
step={ 1 }
|
||||
value={ score }
|
||||
onChange={ event => updateScore(event as number) } />
|
||||
<Text small>{ score }</Text>
|
||||
</div>
|
||||
</WiredConditionBaseView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,130 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { NitroInput } from '../../../../layout';
|
||||
import { WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||
import { WiredConditionBaseView } from './WiredConditionBaseView';
|
||||
|
||||
const ENTITY_HABBO = 1;
|
||||
const ENTITY_PET = 2;
|
||||
const ENTITY_BOT = 4;
|
||||
const AVATAR_MODE_ANY = 0;
|
||||
const AVATAR_MODE_CERTAIN = 1;
|
||||
const SOURCE_SPECIFIED_USERNAME = 101;
|
||||
|
||||
const MATCH_USER_SOURCES: WiredSourceOption[] = [
|
||||
{ value: 0, label: 'wiredfurni.params.sources.users.0' },
|
||||
{ value: 200, label: 'wiredfurni.params.sources.users.200' },
|
||||
{ value: 201, label: 'wiredfurni.params.sources.users.201' }
|
||||
];
|
||||
|
||||
const COMPARE_USER_SOURCES: WiredSourceOption[] = [
|
||||
...MATCH_USER_SOURCES,
|
||||
{ value: SOURCE_SPECIFIED_USERNAME, label: 'wiredfurni.params.sources.users.101' }
|
||||
];
|
||||
|
||||
export const WiredConditionTriggererMatchView: FC<{}> = () =>
|
||||
{
|
||||
const [ entityType, setEntityType ] = useState(ENTITY_HABBO);
|
||||
const [ avatarMode, setAvatarMode ] = useState(AVATAR_MODE_ANY);
|
||||
const [ username, setUsername ] = useState('');
|
||||
const [ matchUserSource, setMatchUserSource ] = useState(0);
|
||||
const [ compareUserSource, setCompareUserSource ] = useState(0);
|
||||
const [ quantifier, setQuantifier ] = useState(0);
|
||||
const [ showAdvanced, setShowAdvanced ] = useState(false);
|
||||
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
|
||||
|
||||
const needsUsername = (avatarMode === AVATAR_MODE_CERTAIN) || (compareUserSource === SOURCE_SPECIFIED_USERNAME);
|
||||
|
||||
const save = () =>
|
||||
{
|
||||
setIntParams([
|
||||
entityType,
|
||||
avatarMode,
|
||||
matchUserSource,
|
||||
compareUserSource,
|
||||
quantifier
|
||||
]);
|
||||
setStringParam(username);
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
|
||||
setEntityType((trigger.intData.length > 0) ? trigger.intData[0] : ENTITY_HABBO);
|
||||
setAvatarMode((trigger.intData.length > 1) ? trigger.intData[1] : AVATAR_MODE_ANY);
|
||||
setMatchUserSource((trigger.intData.length > 2) ? trigger.intData[2] : 0);
|
||||
setCompareUserSource((trigger.intData.length > 3) ? trigger.intData[3] : 0);
|
||||
setQuantifier((trigger.intData.length > 4) ? trigger.intData[4] : 0);
|
||||
setUsername(trigger.stringData || '');
|
||||
setShowAdvanced((trigger.intData.length > 2) ? (trigger.intData[2] !== 0 || trigger.intData[3] !== 0 || trigger.intData[4] !== 0) : false);
|
||||
}, [ trigger ]);
|
||||
|
||||
return (
|
||||
<WiredConditionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE }
|
||||
save={ save }
|
||||
footer={
|
||||
<div className="flex flex-col gap-2">
|
||||
<button className="btn btn-link p-0 align-self-start" type="button" onClick={ () => setShowAdvanced(value => !value) }>
|
||||
{ LocalizeText(showAdvanced ? 'wiredfurni.params.sources.collapse' : 'wiredfurni.params.sources.expand') }
|
||||
</button>
|
||||
{ showAdvanced &&
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
|
||||
{ [ 0, 1 ].map(value =>
|
||||
{
|
||||
return (
|
||||
<div key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" id={ `triggererMatchQuantifier${ value }` } name="triggererMatchQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.quantifier.users.${ value }`) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<WiredSourcesSelector
|
||||
showUsers={ true }
|
||||
userSource={ matchUserSource }
|
||||
userSources={ MATCH_USER_SOURCES }
|
||||
usersTitle="wiredfurni.params.sources.users.title.match.0"
|
||||
onChangeUsers={ setMatchUserSource } />
|
||||
<WiredSourcesSelector
|
||||
showUsers={ true }
|
||||
userSource={ compareUserSource }
|
||||
userSources={ COMPARE_USER_SOURCES }
|
||||
usersTitle="wiredfurni.params.sources.users.title.match.1"
|
||||
onChangeUsers={ setCompareUserSource } />
|
||||
</> }
|
||||
</div>
|
||||
}>
|
||||
<div className="flex flex-col gap-2">
|
||||
{ [ ENTITY_HABBO, ENTITY_PET, ENTITY_BOT ].map(value =>
|
||||
{
|
||||
return (
|
||||
<div key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (entityType === value) } className="form-check-input" id={ `triggererEntityType${ value }` } name="triggererEntityType" type="radio" onChange={ () => setEntityType(value) } />
|
||||
<Text>{ LocalizeText(`wiredfurni.params.usertype.${ value }`) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.picktriggerer') }</Text>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ (avatarMode === AVATAR_MODE_ANY) } className="form-check-input" id="triggererAvatarMode0" name="triggererAvatarMode" type="radio" onChange={ () => setAvatarMode(AVATAR_MODE_ANY) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.anyavatar') }</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ (avatarMode === AVATAR_MODE_CERTAIN) } className="form-check-input" id="triggererAvatarMode1" name="triggererAvatarMode" type="radio" onChange={ () => setAvatarMode(AVATAR_MODE_CERTAIN) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.certainavatar') }</Text>
|
||||
</div>
|
||||
{ needsUsername &&
|
||||
<NitroInput type="text" value={ username } onChange={ event => setUsername(event.target.value) } /> }
|
||||
</div>
|
||||
</WiredConditionBaseView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,151 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||
import { WiredConditionBaseView } from './WiredConditionBaseView';
|
||||
|
||||
const ACTION_WAVE = 1;
|
||||
const ACTION_BLOW_KISS = 2;
|
||||
const ACTION_LAUGH = 3;
|
||||
const ACTION_AWAKE = 4;
|
||||
const ACTION_RELAX = 5;
|
||||
const ACTION_SIT = 6;
|
||||
const ACTION_STAND = 7;
|
||||
const ACTION_LAY = 8;
|
||||
const ACTION_SIGN = 9;
|
||||
const ACTION_DANCE = 10;
|
||||
const ACTION_THUMB_UP = 11;
|
||||
|
||||
const ACTION_OPTIONS = [
|
||||
{ value: ACTION_WAVE, label: 'widget.memenu.wave' },
|
||||
{ value: ACTION_BLOW_KISS, label: 'widget.memenu.blow' },
|
||||
{ value: ACTION_LAUGH, label: 'widget.memenu.laugh' },
|
||||
{ value: ACTION_THUMB_UP, label: 'widget.memenu.thumb' },
|
||||
{ value: ACTION_AWAKE, label: 'wiredfurni.params.action.4' },
|
||||
{ value: ACTION_RELAX, label: 'avatar.widget.random_walk' },
|
||||
{ value: ACTION_SIT, label: 'widget.memenu.sit' },
|
||||
{ value: ACTION_STAND, label: 'widget.memenu.stand' },
|
||||
{ value: ACTION_LAY, label: 'wiredfurni.params.action.8' },
|
||||
{ value: ACTION_SIGN, label: 'widget.memenu.sign' },
|
||||
{ value: ACTION_DANCE, label: 'widget.memenu.dance' }
|
||||
];
|
||||
|
||||
const SIGN_OPTIONS = Array.from({ length: 18 }, (_, value) => ({
|
||||
value,
|
||||
label: `wiredfurni.params.action.sign.${ value }`
|
||||
}));
|
||||
|
||||
const DANCE_OPTIONS = [
|
||||
{ value: 1, label: 'widget.memenu.dance1' },
|
||||
{ value: 2, label: 'widget.memenu.dance2' },
|
||||
{ value: 3, label: 'widget.memenu.dance3' },
|
||||
{ value: 4, label: 'widget.memenu.dance4' }
|
||||
];
|
||||
|
||||
const USER_ACTION_SOURCES: WiredSourceOption[] = [
|
||||
{ value: 0, label: 'wiredfurni.params.sources.users.0' },
|
||||
{ value: 200, label: 'wiredfurni.params.sources.users.200' },
|
||||
{ value: 201, label: 'wiredfurni.params.sources.users.201' }
|
||||
];
|
||||
|
||||
interface WiredConditionUserPerformsActionViewProps
|
||||
{
|
||||
negative?: boolean;
|
||||
}
|
||||
|
||||
export const WiredConditionUserPerformsActionView: FC<WiredConditionUserPerformsActionViewProps> = props =>
|
||||
{
|
||||
const { negative = false } = props;
|
||||
const [ selectedAction, setSelectedAction ] = useState(ACTION_WAVE);
|
||||
const [ signFilterEnabled, setSignFilterEnabled ] = useState(false);
|
||||
const [ signId, setSignId ] = useState(0);
|
||||
const [ danceFilterEnabled, setDanceFilterEnabled ] = useState(false);
|
||||
const [ danceId, setDanceId ] = useState(1);
|
||||
const [ userSource, setUserSource ] = useState(0);
|
||||
const [ quantifier, setQuantifier ] = useState(0);
|
||||
const [ showAdvanced, setShowAdvanced ] = useState(false);
|
||||
const { trigger = null, setIntParams = null } = useWired();
|
||||
const quantifierKeyPrefix = negative ? 'wiredfurni.params.quantifier.users.neg' : 'wiredfurni.params.quantifier.users';
|
||||
|
||||
const save = () => setIntParams([
|
||||
selectedAction,
|
||||
signFilterEnabled ? 1 : 0,
|
||||
signId,
|
||||
danceFilterEnabled ? 1 : 0,
|
||||
danceId,
|
||||
userSource,
|
||||
quantifier
|
||||
]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setSelectedAction((trigger?.intData?.length > 0) ? trigger.intData[0] : ACTION_WAVE);
|
||||
setSignFilterEnabled((trigger?.intData?.length > 1) ? (trigger.intData[1] === 1) : false);
|
||||
setSignId((trigger?.intData?.length > 2) ? trigger.intData[2] : 0);
|
||||
setDanceFilterEnabled((trigger?.intData?.length > 3) ? (trigger.intData[3] === 1) : false);
|
||||
setDanceId((trigger?.intData?.length > 4) ? trigger.intData[4] : 1);
|
||||
setUserSource((trigger?.intData?.length > 5) ? trigger.intData[5] : 0);
|
||||
setQuantifier((trigger?.intData?.length > 6) ? trigger.intData[6] : 0);
|
||||
setShowAdvanced((trigger?.intData?.length > 5) ? (trigger.intData[5] !== 0 || trigger.intData[6] !== 0) : false);
|
||||
}, [ trigger ]);
|
||||
|
||||
return (
|
||||
<WiredConditionBaseView
|
||||
hasSpecialInput={ true }
|
||||
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE }
|
||||
save={ save }
|
||||
footer={
|
||||
<div className="flex flex-col gap-2">
|
||||
<button className="btn btn-link p-0 align-self-start" type="button" onClick={ () => setShowAdvanced(value => !value) }>
|
||||
{ LocalizeText(showAdvanced ? 'wiredfurni.params.sources.collapse' : 'wiredfurni.params.sources.expand') }
|
||||
</button>
|
||||
{ showAdvanced &&
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.quantifier_selection') }</Text>
|
||||
{ [ 0, 1 ].map(value =>
|
||||
{
|
||||
return (
|
||||
<div key={ value } className="flex items-center gap-1">
|
||||
<input checked={ (quantifier === value) } className="form-check-input" id={ `userActionQuantifier${ value }` } name="userActionQuantifier" type="radio" onChange={ () => setQuantifier(value) } />
|
||||
<Text>{ LocalizeText(`${ quantifierKeyPrefix }.${ value }`) }</Text>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<WiredSourcesSelector showUsers={ true } userSource={ userSource } userSources={ USER_ACTION_SOURCES } onChangeUsers={ setUserSource } />
|
||||
</> }
|
||||
</div>
|
||||
}>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text bold>Action</Text>
|
||||
<select className="form-select form-select-sm" value={ selectedAction } onChange={ event => setSelectedAction(parseInt(event.target.value)) }>
|
||||
{ ACTION_OPTIONS.map(option => <option key={ option.value } value={ option.value }>{ LocalizeText(option.label) }</option>) }
|
||||
</select>
|
||||
</div>
|
||||
{ (selectedAction === ACTION_SIGN) &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ signFilterEnabled } className="form-check-input" id="conditionSignFilterEnabled" type="checkbox" onChange={ event => setSignFilterEnabled(event.target.checked) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.sign_filter') }</Text>
|
||||
</div>
|
||||
{ signFilterEnabled &&
|
||||
<select className="form-select form-select-sm" value={ signId } onChange={ event => setSignId(parseInt(event.target.value)) }>
|
||||
{ SIGN_OPTIONS.map(option => <option key={ option.value } value={ option.value }>{ LocalizeText(option.label) }</option>) }
|
||||
</select> }
|
||||
</div> }
|
||||
{ (selectedAction === ACTION_DANCE) &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ danceFilterEnabled } className="form-check-input" id="conditionDanceFilterEnabled" type="checkbox" onChange={ event => setDanceFilterEnabled(event.target.checked) } />
|
||||
<Text>{ LocalizeText('wiredfurni.params.dance_filter') }</Text>
|
||||
</div>
|
||||
{ danceFilterEnabled &&
|
||||
<select className="form-select form-select-sm" value={ danceId } onChange={ event => setDanceId(parseInt(event.target.value)) }>
|
||||
{ DANCE_OPTIONS.map(option => <option key={ option.value } value={ option.value }>{ LocalizeText(option.label) }</option>) }
|
||||
</select> }
|
||||
</div> }
|
||||
</WiredConditionBaseView>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user