You've already forked AstralRinth
forked from didirus/AstralRinth
Moderation Checklist Fixes (#3986)
* fix: DEV-164 * fix: dev-163 * feat: DEV-162
This commit is contained in:
@@ -116,8 +116,10 @@
|
|||||||
<span class="label__title">{{ action.label }}</span>
|
<span class="label__title">{{ action.label }}</span>
|
||||||
</label>
|
</label>
|
||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
|
:max-visible-options="3"
|
||||||
|
render-up
|
||||||
:name="`dropdown-${getActionId(action)}`"
|
:name="`dropdown-${getActionId(action)}`"
|
||||||
:options="action.options"
|
:options="getVisibleDropdownOptions(action)"
|
||||||
:model-value="getDropdownValue(action)"
|
:model-value="getDropdownValue(action)"
|
||||||
:placeholder="'Select an option'"
|
:placeholder="'Select an option'"
|
||||||
:disabled="false"
|
:disabled="false"
|
||||||
@@ -138,7 +140,7 @@
|
|||||||
<div class="mb-2 font-semibold">{{ action.label }}</div>
|
<div class="mb-2 font-semibold">{{ action.label }}</div>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<ButtonStyled
|
<ButtonStyled
|
||||||
v-for="(option, optIndex) in action.options"
|
v-for="(option, optIndex) in getVisibleMultiSelectOptions(action)"
|
||||||
:key="`${getActionId(action)}-chip-${optIndex}`"
|
:key="`${getActionId(action)}-chip-${optIndex}`"
|
||||||
:color="isChipSelected(action, optIndex) ? 'brand' : 'standard'"
|
:color="isChipSelected(action, optIndex) ? 'brand' : 'standard'"
|
||||||
@click="toggleChip(action, optIndex)"
|
@click="toggleChip(action, optIndex)"
|
||||||
@@ -559,14 +561,20 @@ function handleKeybinds(event: KeyboardEvent) {
|
|||||||
},
|
},
|
||||||
trySelectDropdownOption: (actionIndex: number, optionIndex: number) => {
|
trySelectDropdownOption: (actionIndex: number, optionIndex: number) => {
|
||||||
const action = visibleActions.value[actionIndex] as DropdownAction;
|
const action = visibleActions.value[actionIndex] as DropdownAction;
|
||||||
if (action && action.type === "dropdown" && action.options[optionIndex]) {
|
if (action && action.type === "dropdown") {
|
||||||
selectDropdownOption(action, action.options[optionIndex]);
|
const visibleOptions = getVisibleDropdownOptions(action);
|
||||||
|
if (optionIndex < visibleOptions.length) {
|
||||||
|
selectDropdownOption(action, visibleOptions[optionIndex]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tryToggleChip: (actionIndex: number, chipIndex: number) => {
|
tryToggleChip: (actionIndex: number, chipIndex: number) => {
|
||||||
const action = visibleActions.value[actionIndex] as MultiSelectChipsAction;
|
const action = visibleActions.value[actionIndex] as MultiSelectChipsAction;
|
||||||
if (action && action.type === "multi-select-chips") {
|
if (action && action.type === "multi-select-chips") {
|
||||||
toggleChip(action, chipIndex);
|
const visibleOptions = getVisibleMultiSelectOptions(action);
|
||||||
|
if (chipIndex < visibleOptions.length) {
|
||||||
|
toggleChip(action, chipIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -733,13 +741,17 @@ const multiSelectActions = computed(() =>
|
|||||||
|
|
||||||
function getDropdownValue(action: DropdownAction) {
|
function getDropdownValue(action: DropdownAction) {
|
||||||
const actionId = getActionId(action);
|
const actionId = getActionId(action);
|
||||||
|
const visibleOptions = getVisibleDropdownOptions(action);
|
||||||
const currentValue = actionStates.value[actionId]?.value ?? action.defaultOption ?? 0;
|
const currentValue = actionStates.value[actionId]?.value ?? action.defaultOption ?? 0;
|
||||||
|
|
||||||
if (action.options && action.options[currentValue]) {
|
const allOptions = action.options;
|
||||||
return action.options[currentValue];
|
const storedOption = allOptions[currentValue];
|
||||||
|
|
||||||
|
if (storedOption && visibleOptions.includes(storedOption)) {
|
||||||
|
return storedOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
return action.options?.[0] || null;
|
return visibleOptions[0] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isActionSelected(action: Action): boolean {
|
function isActionSelected(action: Action): boolean {
|
||||||
@@ -775,20 +787,31 @@ function selectDropdownOption(action: DropdownAction, selected: any) {
|
|||||||
function isChipSelected(action: MultiSelectChipsAction, optionIndex: number): boolean {
|
function isChipSelected(action: MultiSelectChipsAction, optionIndex: number): boolean {
|
||||||
const actionId = getActionId(action);
|
const actionId = getActionId(action);
|
||||||
const selectedSet = actionStates.value[actionId]?.value as Set<number> | undefined;
|
const selectedSet = actionStates.value[actionId]?.value as Set<number> | undefined;
|
||||||
return selectedSet?.has(optionIndex) || false;
|
|
||||||
|
const visibleOptions = getVisibleMultiSelectOptions(action);
|
||||||
|
const visibleOption = visibleOptions[optionIndex];
|
||||||
|
const originalIndex = action.options.findIndex((opt) => opt === visibleOption);
|
||||||
|
|
||||||
|
return selectedSet?.has(originalIndex) || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleChip(action: MultiSelectChipsAction, optionIndex: number) {
|
function toggleChip(action: MultiSelectChipsAction, optionIndex: number) {
|
||||||
const actionId = getActionId(action);
|
const actionId = getActionId(action);
|
||||||
const state = actionStates.value[actionId];
|
const state = actionStates.value[actionId];
|
||||||
if (state && state.value instanceof Set) {
|
if (state && state.value instanceof Set) {
|
||||||
if (state.value.has(optionIndex)) {
|
const visibleOptions = getVisibleMultiSelectOptions(action);
|
||||||
state.value.delete(optionIndex);
|
const visibleOption = visibleOptions[optionIndex];
|
||||||
} else {
|
const originalIndex = action.options.findIndex((opt) => opt === visibleOption);
|
||||||
state.value.add(optionIndex);
|
|
||||||
|
if (originalIndex !== -1) {
|
||||||
|
if (state.value.has(originalIndex)) {
|
||||||
|
state.value.delete(originalIndex);
|
||||||
|
} else {
|
||||||
|
state.value.add(originalIndex);
|
||||||
|
}
|
||||||
|
state.selected = state.value.size > 0;
|
||||||
|
persistState();
|
||||||
}
|
}
|
||||||
state.selected = state.value.size > 0;
|
|
||||||
persistState();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -869,9 +892,23 @@ async function processAction(
|
|||||||
stageIndex: number,
|
stageIndex: number,
|
||||||
messageParts: MessagePart[],
|
messageParts: MessagePart[],
|
||||||
) {
|
) {
|
||||||
|
const allValidActionIds: string[] = [];
|
||||||
|
checklist.forEach((stage, stageIdx) => {
|
||||||
|
stage.actions.forEach((stageAction, actionIdx) => {
|
||||||
|
allValidActionIds.push(getActionIdForStage(stageAction, stageIdx, actionIdx));
|
||||||
|
if (stageAction.enablesActions) {
|
||||||
|
stageAction.enablesActions.forEach((enabledAction, enabledIdx) => {
|
||||||
|
allValidActionIds.push(
|
||||||
|
getActionIdForStage(enabledAction, stageIdx, actionIdx, enabledIdx),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (action.type === "button" || action.type === "toggle") {
|
if (action.type === "button" || action.type === "toggle") {
|
||||||
const buttonAction = action as ButtonAction | ToggleAction;
|
const buttonAction = action as ButtonAction | ToggleAction;
|
||||||
const message = await getActionMessage(buttonAction, selectedActionIds);
|
const message = await getActionMessage(buttonAction, selectedActionIds, allValidActionIds);
|
||||||
if (message) {
|
if (message) {
|
||||||
messageParts.push({
|
messageParts.push({
|
||||||
weight: buttonAction.weight,
|
weight: buttonAction.weight,
|
||||||
@@ -885,6 +922,7 @@ async function processAction(
|
|||||||
const matchingVariant = findMatchingVariant(
|
const matchingVariant = findMatchingVariant(
|
||||||
conditionalAction.messageVariants,
|
conditionalAction.messageVariants,
|
||||||
selectedActionIds,
|
selectedActionIds,
|
||||||
|
allValidActionIds,
|
||||||
);
|
);
|
||||||
if (matchingVariant) {
|
if (matchingVariant) {
|
||||||
const message = (await matchingVariant.message()) as string;
|
const message = (await matchingVariant.message()) as string;
|
||||||
@@ -944,6 +982,24 @@ function shouldShowAction(action: Action): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getVisibleDropdownOptions(action: DropdownAction) {
|
||||||
|
return action.options.filter((option) => {
|
||||||
|
if (typeof option.shouldShow === "function") {
|
||||||
|
return option.shouldShow(props.project);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVisibleMultiSelectOptions(action: MultiSelectChipsAction) {
|
||||||
|
return action.options.filter((option) => {
|
||||||
|
if (typeof option.shouldShow === "function") {
|
||||||
|
return option.shouldShow(props.project);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function shouldShowStageIndex(stageIndex: number): boolean {
|
function shouldShowStageIndex(stageIndex: number): boolean {
|
||||||
return shouldShowStage(checklist[stageIndex]);
|
return shouldShowStage(checklist[stageIndex]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,6 +153,13 @@ export interface DropdownActionOption extends WeightedMessage {
|
|||||||
* The label of the option, which is displayed to the moderator.
|
* The label of the option, which is displayed to the moderator.
|
||||||
*/
|
*/
|
||||||
label: string
|
label: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that determines whether this option should be shown for a given project.
|
||||||
|
*
|
||||||
|
* By default, it returns `true`, meaning the option is always shown.
|
||||||
|
*/
|
||||||
|
shouldShow?: (project: Project) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DropdownAction extends BaseAction {
|
export interface DropdownAction extends BaseAction {
|
||||||
@@ -179,6 +186,13 @@ export interface MultiSelectChipsOption extends WeightedMessage {
|
|||||||
* The label of the chip, which is displayed to the moderator.
|
* The label of the chip, which is displayed to the moderator.
|
||||||
*/
|
*/
|
||||||
label: string
|
label: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that determines whether this option should be shown for a given project.
|
||||||
|
*
|
||||||
|
* By default, it returns `true`, meaning the option is always shown.
|
||||||
|
*/
|
||||||
|
shouldShow?: (project: Project) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MultiSelectChipsAction extends BaseAction {
|
export interface MultiSelectChipsAction extends BaseAction {
|
||||||
|
|||||||
@@ -125,13 +125,19 @@ export function processMessage(
|
|||||||
export function findMatchingVariant(
|
export function findMatchingVariant(
|
||||||
variants: ConditionalMessage[],
|
variants: ConditionalMessage[],
|
||||||
selectedActionIds: string[],
|
selectedActionIds: string[],
|
||||||
|
allValidActionIds?: string[],
|
||||||
): ConditionalMessage | null {
|
): ConditionalMessage | null {
|
||||||
for (const variant of variants) {
|
for (const variant of variants) {
|
||||||
const conditions = variant.conditions
|
const conditions = variant.conditions
|
||||||
|
|
||||||
const meetsRequired =
|
const meetsRequired =
|
||||||
!conditions.requiredActions ||
|
!conditions.requiredActions ||
|
||||||
conditions.requiredActions.every((id) => selectedActionIds.includes(id))
|
conditions.requiredActions.every((id) => {
|
||||||
|
if (allValidActionIds && !allValidActionIds.includes(id)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return selectedActionIds.includes(id)
|
||||||
|
})
|
||||||
|
|
||||||
const meetsExcluded =
|
const meetsExcluded =
|
||||||
!conditions.excludedActions ||
|
!conditions.excludedActions ||
|
||||||
@@ -148,9 +154,14 @@ export function findMatchingVariant(
|
|||||||
export async function getActionMessage(
|
export async function getActionMessage(
|
||||||
action: ButtonAction | ToggleAction,
|
action: ButtonAction | ToggleAction,
|
||||||
selectedActionIds: string[],
|
selectedActionIds: string[],
|
||||||
|
allValidActionIds?: string[],
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if (action.conditionalMessages && action.conditionalMessages.length > 0) {
|
if (action.conditionalMessages && action.conditionalMessages.length > 0) {
|
||||||
const matchingConditional = findMatchingVariant(action.conditionalMessages, selectedActionIds)
|
const matchingConditional = findMatchingVariant(
|
||||||
|
action.conditionalMessages,
|
||||||
|
selectedActionIds,
|
||||||
|
allValidActionIds,
|
||||||
|
)
|
||||||
if (matchingConditional) {
|
if (matchingConditional) {
|
||||||
return (await matchingConditional.message()) as string
|
return (await matchingConditional.message()) as string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,10 @@ const props = defineProps({
|
|||||||
type: Function,
|
type: Function,
|
||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
|
maxVisibleOptions: {
|
||||||
|
type: Number,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function getOptionLabel(option) {
|
function getOptionLabel(option) {
|
||||||
@@ -263,7 +267,7 @@ const isChildOfDropdown = (element) => {
|
|||||||
|
|
||||||
.options {
|
.options {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
max-height: 18.75rem;
|
max-height: v-bind('maxVisibleOptions ? `calc(${maxVisibleOptions} * 3rem)` : "18.75rem"');
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
var(--shadow-inset-sm),
|
var(--shadow-inset-sm),
|
||||||
|
|||||||
Reference in New Issue
Block a user