Skip to content

Commit 4ccf4d5

Browse files
authored
feat: allow toggles unselect other toggles (#8095)
1 parent b449d81 commit 4ccf4d5

6 files changed

Lines changed: 79 additions & 34 deletions

File tree

‎apps/frontend/src/components/project-review/issue-list/issue-toggles.vue‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,11 @@ function updateRow(row: ToggleRow, values: string[]) {
197197
)
198198
)
199199
panels.addIssue(props.issue.id)
200-
for (const entry of row.entries) {
201-
if (!panels.selected(entry.binding, entry.control) && next.has(optionValue(entry)))
202-
panels.write(entry.binding, entry.control, true)
200+
const added = row.entries.filter(
201+
(entry) => !panels.selected(entry.binding, entry.control) && next.has(optionValue(entry)),
202+
)
203+
for (const entry of added) {
204+
panels.write(entry.binding, entry.control, true)
203205
}
204206
for (const entry of row.entries) {
205207
if (panels.selected(entry.binding, entry.control) && !next.has(optionValue(entry)))
@@ -210,9 +212,11 @@ function updateRow(row: ToggleRow, values: string[]) {
210212
function updateOptions(values: string[]) {
211213
if (pending.value || props.disabled) return
212214
const next = new Set(values)
213-
for (const entry of optionControls.value) {
214-
if (!panels.selected(entry.binding, entry.control) && next.has(optionValue(entry)))
215-
panels.write(entry.binding, entry.control, true)
215+
const added = optionControls.value.filter(
216+
(entry) => !panels.selected(entry.binding, entry.control) && next.has(optionValue(entry)),
217+
)
218+
for (const entry of added) {
219+
panels.write(entry.binding, entry.control, true)
216220
}
217221
for (const entry of optionControls.value) {
218222
if (panels.selected(entry.binding, entry.control) && !next.has(optionValue(entry)))
@@ -222,7 +226,10 @@ function updateOptions(values: string[]) {
222226
223227
function changeOptions(values: string[]) {
224228
optionDraft.value = values
225-
if (values.length) updateOptions(values)
229+
if (values.length) {
230+
updateOptions(values)
231+
optionDraft.value = [...selectedOptionValues.value]
232+
}
226233
}
227234
228235
function openOptions() {

‎apps/frontend/src/providers/project-review/review-panels.ts‎

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export type ResolvedIssueControl = ResolvedIssueControlOptions &
3535
| {
3636
type: 'toggle'
3737
id?: string
38+
unselectToggles?: readonly string[]
3839
issueCardDisplay?: {
3940
icon?: string
4041
label: string
@@ -329,11 +330,11 @@ export function createReviewPanels(
329330
const bindings = new Map<string, ReviewPanelBinding>()
330331
const projectV3 = project.value
331332
if (!projectV3) return bindings
332-
const issueIds = preview
333-
? [...new Set([...selectedIssueIds.value, preview.control.issueId])]
334-
: selectedIssueIds.value
335333
const toggleIds = (issueId: string) => {
336334
const ids = new Set(selectedToggleIds(projectV3.id, issueId))
335+
if (preview?.control.type === 'toggle') {
336+
for (const id of preview.control.unselectToggles ?? []) ids.delete(id)
337+
}
337338
if (preview?.control.type === 'toggle' && preview.control.issueId === issueId) {
338339
for (const id of preview.control.childToggleIds ??
339340
(preview.control.id === undefined ? [] : [preview.control.id])) {
@@ -342,6 +343,17 @@ export function createReviewPanels(
342343
}
343344
return [...ids]
344345
}
346+
const issueIds = preview
347+
? [
348+
...new Set([
349+
...selectedIssueIds.value.filter(
350+
(id) =>
351+
session.read(projectV3.id, 'issue-active')[id] === true || toggleIds(id).length > 0,
352+
),
353+
preview.control.issueId,
354+
]),
355+
]
356+
: selectedIssueIds.value
345357
const showAllIssueControls = reviewData.value.showAllIssueControls === true
346358
const context: ReviewContext = {
347359
projectV3,
@@ -430,6 +442,7 @@ export function createReviewPanels(
430442
...options,
431443
type: 'toggle',
432444
id: node.id,
445+
unselectToggles: node.unselectToggles,
433446
issueCardDisplay: node.issueCardDisplay
434447
? {
435448
icon: resolveWithContext(node.issueCardDisplay.icon, issueContext),
@@ -522,6 +535,14 @@ export function createReviewPanels(
522535
control.childToggleIds = children.flatMap((item) =>
523536
item.type === 'toggle' && !item.disabled && item.id ? [item.id] : [],
524537
)
538+
control.unselectToggles = [
539+
...new Set([
540+
...(control.unselectToggles ?? []),
541+
...children.flatMap((item) =>
542+
item.type === 'toggle' && !item.disabled ? (item.unselectToggles ?? []) : [],
543+
),
544+
]),
545+
]
525546
control.disabled ||= control.childToggleIds.length === 0
526547
}
527548
}
@@ -645,6 +666,16 @@ export function createReviewPanels(
645666
return
646667
}
647668
if (typeof value !== 'boolean') return
669+
if (value && current.unselectToggles?.length) {
670+
const toggleIdsToUnselect = new Set(current.unselectToggles)
671+
for (const [issueId, selected] of Object.entries(session.read(binding.projectId, 'issues'))) {
672+
if (!(selected instanceof Set) || ![...selected].some((id) => toggleIdsToUnselect.has(id)))
673+
continue
674+
const keys = new Set([...selected].filter((id) => !toggleIdsToUnselect.has(id)))
675+
session.write(binding.projectId, 'issues', issueId, keys.size ? keys : undefined)
676+
if (issueId !== current.issueId) updateIssueOrder(binding.projectId, issueId)
677+
}
678+
}
648679
if (current.childToggleIds) {
649680
const keys = new Set(selectedToggleIds(binding.projectId, current.issueId))
650681
for (const id of current.childToggleIds) {
@@ -839,22 +870,21 @@ export function createReviewPanels(
839870
sharedInputs.textValues[control.key] = textValue(binding, control)
840871
continue
841872
}
873+
const previewSelected =
874+
control.type === 'toggle' &&
875+
control.issueId === preview?.control.issueId &&
876+
preview.control.type === 'toggle' &&
877+
(control.id === preview.control.id ||
878+
(control.id !== undefined && preview.control.childToggleIds?.includes(control.id)))
842879
if (
843880
control.childToggleIds ||
844881
control.disabled ||
845882
(control.type === 'toggle' &&
846-
!(
847-
binding.key === preview?.binding.key &&
848-
control.issueId === preview.control.issueId &&
849-
preview.control.type === 'toggle' &&
850-
control.id === preview.control.id
851-
) &&
852-
!(
853-
control.issueId === preview?.control.issueId &&
854-
control.id !== undefined &&
855-
preview.control.childToggleIds?.includes(control.id)
856-
) &&
857-
!selected(binding, control))
883+
!previewSelected &&
884+
(!selected(binding, control) ||
885+
(preview?.control.type === 'toggle' &&
886+
control.id !== undefined &&
887+
preview.control.unselectToggles?.includes(control.id))))
858888
)
859889
continue
860890
const selection: IssueSelection = issues.get(control.issueId) ?? {
@@ -896,7 +926,7 @@ export function createReviewPanels(
896926
selected: {
897927
items: {},
898928
issueIds: preview
899-
? [...new Set([...selectedIssueIds.value, preview.control.issueId])]
929+
? [...issues].filter(([, { active }]) => active).map(([id]) => id)
900930
: selectedIssueIds.value,
901931
toggleIds: [...keys],
902932
},

‎packages/moderation/src/data/issues/component-builders/item-panels.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ export function expandItemReviewPanels(
209209
: (ctx) => resolve(node.placeholder, scopedContext(ctx, item)) ?? ''
210210
}
211211
if (node.type !== 'toggle') return [scoped]
212+
if (scoped.type === 'toggle') {
213+
scoped.unselectToggles = node.unselectToggles?.map((id) => `${item.key}:${id}`)
214+
}
212215
if (scoped.type === 'toggle' && node.issueCardDisplay !== undefined) {
213216
const display = node.issueCardDisplay
214217
scoped.issueCardDisplay = {

‎packages/moderation/src/data/issues/component-builders/types.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ export interface IssueToggle extends IssueControlOptions {
9595
* Without an ID, the toggle activates the issue directly and contributes no selected toggle ID.
9696
*/
9797
id?: string
98+
/** Explicit toggle IDs to clear across issues when enabled. Item panels scope these IDs to the current item. */
99+
unselectToggles?: readonly string[]
98100
}
99101

100102
export type IssueToggleConfig = Omit<IssueToggle, 'type'>

‎packages/moderation/src/data/issues/disclosures.ts‎

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -468,9 +468,10 @@ export const telemetryDisclosureReviewPanel = panel({
468468
label: 'Telemetry: Opt In',
469469
group: 'Missing',
470470
},
471-
disabled: ({ selected }) =>
472-
selected.toggleIds.includes('telemetry-disclosure-missing-opt-out') ||
473-
selected.toggleIds.includes('telemetry-disclosure-missing-always'),
471+
unselectToggles: [
472+
'telemetry-disclosure-missing-opt-out',
473+
'telemetry-disclosure-missing-always',
474+
],
474475
}),
475476
toggle({
476477
issue: disclosuresIssue,
@@ -480,9 +481,10 @@ export const telemetryDisclosureReviewPanel = panel({
480481
label: 'Telemetry: Opt Out',
481482
group: 'Missing',
482483
},
483-
disabled: ({ selected }) =>
484-
selected.toggleIds.includes('telemetry-disclosure-missing-opt-in') ||
485-
selected.toggleIds.includes('telemetry-disclosure-missing-always'),
484+
unselectToggles: [
485+
'telemetry-disclosure-missing-opt-in',
486+
'telemetry-disclosure-missing-always',
487+
],
486488
}),
487489
toggle({
488490
issue: disclosuresIssue,
@@ -492,9 +494,10 @@ export const telemetryDisclosureReviewPanel = panel({
492494
label: 'Telemetry: Always Online',
493495
group: 'Missing',
494496
},
495-
disabled: ({ selected }) =>
496-
selected.toggleIds.includes('telemetry-disclosure-missing-opt-in') ||
497-
selected.toggleIds.includes('telemetry-disclosure-missing-opt-out'),
497+
unselectToggles: [
498+
'telemetry-disclosure-missing-opt-in',
499+
'telemetry-disclosure-missing-opt-out',
500+
],
498501
}),
499502
),
500503
)

‎packages/moderation/src/data/issues/rules.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,13 +207,13 @@ export const rulesReviewPanel = panel({
207207
issue: rulesServerSideOptOutIssue,
208208
label: 'Opt-out',
209209
id: 'rules-server-side-opt-out',
210-
disabled: ({ selected }) => selected.toggleIds.includes('rules-server-side-opt-in'),
210+
unselectToggles: ['rules-server-side-opt-in'],
211211
}),
212212
toggle({
213213
issue: rulesServerSideOptInIssue,
214214
label: 'Opt-in',
215215
id: 'rules-server-side-opt-in',
216-
disabled: ({ selected }) => selected.toggleIds.includes('rules-server-side-opt-out'),
216+
unselectToggles: ['rules-server-side-opt-out'],
217217
}),
218218
toggle({
219219
issue: rulesAiGeneratedIssue,

0 commit comments

Comments
 (0)