undergroundwires/privacy.sexy

View on GitHub
src/presentation/components/Scripts/Menu/Revert/TheRevertSelector.vue

Summary

Maintainability
Test Coverage
<template>
  <MenuOptionList
    v-if="!RevertStatusType.NoReversibleScripts"
    label="Revert"
  >
    <!--
      "None" comes before "Selected" because:
        - "None" is the default and least impactful state, placed first
          for safety and user expectations.
        - Aligns with common UI patterns of 'off' state before 'on'.
        - Helps prevent accidental actions with potential unwanted effects.
    -->
    <!-- None -->
    <TooltipWrapper>
      <MenuOptionListItem
        label="None"
        :enabled="canSetStatus(RevertStatusType.NoScriptsReverted)"
        @click="setRevertStatusType(false)"
      />
      <template #tooltip>
        <RevertStatusDocumentation
          icon="shield"
          description="Applies all selected scripts to protect your privacy."
          :considerations="createConsiderationsConditionally({
            warnNonStandard: (nonStandardCount) =>
              `${nonStandardCount} selected scripts exceed the 'Standard' recommendation level and can significantly change how your system works.`
              + ' Review your selections carefully.',
            warnIrreversibleScripts: (irreversibleCount) =>
              `${irreversibleCount} selected scripts make irreversible changes (such as privacy cleanup) that cannot be undone.`,
          })"
        />
      </template>
    </TooltipWrapper>
    <!-- Selected -->
    <TooltipWrapper>
      <MenuOptionListItem
        label="Selected"
        :enabled="canSetStatus(RevertStatusType.AllScriptsReverted)"
        @click="setRevertStatusType(true)"
      />
      <template #tooltip>
        <RevertStatusDocumentation
          icon="rotate-left"
          description="Reverts selected scripts back to their default settings where possible, balancing system functionality with privacy."
          :considerations="createConsiderationsConditionally({
            warnAlways: ['Reverting changes may reduce the level of privacy protection.'],
            warnIrreversibleScripts: (irreversibleCount) =>
              `${irreversibleCount} selected scripts make irreversible changes (such as privacy cleanup) and will not be reverted.`,
          })"
        />
      </template>
    </TooltipWrapper>
  </MenuOptionList>
</template>

<script lang="ts">
import {
  defineComponent, computed,
} from 'vue';
import { injectKey } from '@/presentation/injectionSymbols';
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
import { RecommendationLevel } from '@/domain/Executables/Script/RecommendationLevel';
import type { Script } from '@/domain/Executables/Script/Script';
import MenuOptionList from '../MenuOptionList.vue';
import MenuOptionListItem from '../MenuOptionListItem.vue';
import RevertStatusDocumentation from './RevertStatusDocumentation.vue';
import { RevertStatusType } from './RevertStatusType';
import { setCurrentRevertStatus, getCurrentRevertStatus } from './RevertStatusHandler';

export default defineComponent({
  components: {
    MenuOptionList,
    MenuOptionListItem,
    TooltipWrapper,
    RevertStatusDocumentation,
  },
  setup() {
    const {
      currentSelection, modifyCurrentSelection,
    } = injectKey((keys) => keys.useUserSelectionState);

    const currentRevertStatusType = computed<RevertStatusType>(
      () => getCurrentRevertStatus(currentSelection.value.scripts),
    );

    const selectedScripts = computed<readonly Script[]>(
      () => currentSelection.value.scripts.selectedScripts.map((s) => s.script),
    );

    const totalIrreversibleScriptsInSelection = computed<number>(() => {
      return selectedScripts.value.filter((s) => !s.canRevert()).length;
    });

    const totalNonStandardScriptsInSelection = computed<number>(() => {
      return selectedScripts.value.filter((s) => s.level !== RecommendationLevel.Standard).length;
    });

    function canSetStatus(status: RevertStatusType): boolean {
      if (currentRevertStatusType.value === RevertStatusType.NoReversibleScripts) {
        return false;
      }
      if (currentRevertStatusType.value === RevertStatusType.SomeScriptsReverted) {
        return true;
      }
      return currentRevertStatusType.value !== status;
    }

    function setRevertStatusType(revert: boolean) {
      modifyCurrentSelection((mutableSelection) => {
        setCurrentRevertStatus(revert, mutableSelection.scripts);
      });
    }

    function createConsiderationsConditionally(messages: {
      readonly warnIrreversibleScripts: (irreversibleCount: number) => string,
      readonly warnNonStandard?: (nonStandard: number) => string,
      readonly warnAlways?: string[];
    }): string[] {
      const considerations = new Array<string>();
      if (messages.warnAlways) {
        considerations.push(...messages.warnAlways);
      }
      const irreversibleCount = totalIrreversibleScriptsInSelection.value;
      if (irreversibleCount !== 0) {
        considerations.push(messages.warnIrreversibleScripts(irreversibleCount));
      }
      if (messages.warnNonStandard) {
        const nonStandardCount = totalNonStandardScriptsInSelection.value;
        if (nonStandardCount !== 0) {
          considerations.push(messages.warnNonStandard(nonStandardCount));
        }
      }
      return considerations;
    }

    return {
      RevertStatusType,
      currentRevertStatusType,
      setRevertStatusType,
      canSetStatus,
      createConsiderationsConditionally,
    };
  },
});
</script>