<commit_before>import { WebApplication } from '@/ui_models/application';
import template from './challenge-modal.pug';
import {
  ChallengeValue,
  removeFromArray,
  Challenge,
  ChallengeReason,
  ChallengePrompt
} from 'snjs';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { WebDirective } from '@/types';
import { confirmDialog } from '@/services/alertService';
import {
  STRING_SIGN_OUT_CONFIRMATION,
  STRING_STORAGE_UPDATE,
  STRING_AUTHENTICATION_REQUIRED,
} from '@/strings';

type InputValue = {
  prompt: ChallengePrompt
  value: string
  invalid: boolean
}

type Values = Record<number, InputValue>

type ChallengeModalState = {
  prompts: ChallengePrompt[]
  values: Partial<Values>
  processing: boolean,
  forgotPasscode: boolean,
  showForgotPasscodeLink: boolean,
  processingPrompts: ChallengePrompt[],
  hasAccount: boolean,
  title: string,
  subtitle: string
}

class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> {
  private $element: JQLite
  application!: WebApplication
  challenge!: Challenge
  private cancelable = false

  /* @ngInject */
  constructor(
    $element: JQLite,
    $timeout: ng.ITimeoutService
  ) {
    super($timeout);
    this.$element = $element;
  }

  getState() {
    return this.state as ChallengeModalState;
  }

  $onInit() {
    super.$onInit();
    const values = {} as Values;
    const prompts = this.challenge.prompts;
    for (const prompt of prompts) {
      values[prompt.id] = {
        prompt,
        value: '',
        invalid: false
      };
    }
    let showForgotPasscodeLink = false;
    switch (this.challenge.reason) {
      case ChallengeReason.ApplicationUnlock:
        showForgotPasscodeLink = true;
        this.cancelable = false;
        break;
      case ChallengeReason.Migration:
        showForgotPasscodeLink = true;
        this.cancelable = false;
        break;
      case ChallengeReason.ProtocolUpgrade:
        showForgotPasscodeLink = false;
        this.cancelable = true;
        break;
      case ChallengeReason.ResaveRootKey:
        showForgotPasscodeLink = false;
        this.cancelable = true;
        break;
    }
    this.cancelable = !showForgotPasscodeLink
    this.setState({
      prompts,
      values,
      processing: false,
      forgotPasscode: false,
      showForgotPasscodeLink,
      hasAccount: this.application.hasAccount(),
      title: this.challenge.title,
      subtitle: this.challenge.subtitle,
      processingPrompts: []
    });
    this.application.addChallengeObserver(
      this.challenge,
      {
        onValidValue: (value) => {
          this.getState().values[value.prompt.id]!.invalid = false;
          removeFromArray(this.state.processingPrompts, value.prompt);
          this.reloadProcessingStatus();
        },
        onInvalidValue: (value) => {
          this.getState().values[value.prompt.id]!.invalid = true;
          /** If custom validation, treat all values together and not individually */
          if (!value.prompt.validates) {
            this.setState({ processingPrompts: [] });
          } else {
            removeFromArray(this.state.processingPrompts, value.prompt);
          }
          this.reloadProcessingStatus();
        },
        onComplete: () => {
          this.dismiss();
        },
        onCancel: () => {
          this.dismiss();
        },
      }
    );
  }

  deinit() {
    (this.application as any) = undefined;
    (this.challenge as any) = undefined;
    super.deinit();
  }

  reloadProcessingStatus() {
    return this.setState({
      processing: this.state.processingPrompts.length > 0
    });
  }

  get modalTitle(): string {
    if (this.challenge.reason === ChallengeReason.Migration) {
      return STRING_STORAGE_UPDATE;
    } else {
      return STRING_AUTHENTICATION_REQUIRED;
    }
  }

  async destroyLocalData() {
    if (await confirmDialog({
      text: STRING_SIGN_OUT_CONFIRMATION,
      confirmButtonStyle: "danger"
    })) {
      await this.application.signOut();
      this.dismiss();
    };
  }

  /** @template */
  cancel() {
    if (this.cancelable) {
      this.application!.cancelChallenge(this.challenge);
    }
  }

  onForgotPasscodeClick() {
    this.setState({
      forgotPasscode: true
    });
  }

  onTextValueChange(prompt: ChallengePrompt) {
    const values = this.getState().values;
    values[prompt.id]!.invalid = false;
    this.setState({ values });
  }

  validate() {
    const failed = [];
    for (const prompt of this.getState().prompts) {
      const value = this.getState().values[prompt.id];
      if (!value || value.value.length === 0) {
        this.getState().values[prompt.id]!.invalid = true;
      }
    }
    return failed.length === 0;
  }

  async submit() {
    if (!this.validate()) {
      return;
    }
    await this.setState({ processing: true });
    const values: ChallengeValue[] = [];
    for (const inputValue of Object.values(this.getState().values)) {
      if (inputValue!.invalid) {
        continue;
      }
      const rawValue = inputValue!!.value;<commit_msg>fix: challenge modal logic<commit_after>import { WebApplication } from '@/ui_models/application';
import template from './challenge-modal.pug';
import {
  ChallengeValue,
  removeFromArray,
  Challenge,
  ChallengeReason,
  ChallengePrompt
} from 'snjs';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { WebDirective } from '@/types';
import { confirmDialog } from '@/services/alertService';
import {
  STRING_SIGN_OUT_CONFIRMATION,
  STRING_STORAGE_UPDATE,
  STRING_AUTHENTICATION_REQUIRED,
} from '@/strings';

type InputValue = {
  prompt: ChallengePrompt
  value: string
  invalid: boolean
}

type Values = Record<number, InputValue>

type ChallengeModalState = {
  prompts: ChallengePrompt[]
  values: Partial<Values>
  processing: boolean,
  forgotPasscode: boolean,
  showForgotPasscodeLink: boolean,
  processingPrompts: ChallengePrompt[],
  hasAccount: boolean,
  title: string,
  subtitle: string
}

class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> {
  private $element: JQLite
  application!: WebApplication
  challenge!: Challenge
  private cancelable = false

  /* @ngInject */
  constructor(
    $element: JQLite,
    $timeout: ng.ITimeoutService
  ) {
    super($timeout);
    this.$element = $element;
  }

  getState() {
    return this.state as ChallengeModalState;
  }

  $onInit() {
    super.$onInit();
    const values = {} as Values;
    const prompts = this.challenge.prompts;
    for (const prompt of prompts) {
      values[prompt.id] = {
        prompt,
        value: '',
        invalid: false
      };
    }
    let showForgotPasscodeLink = false;
    switch (this.challenge.reason) {
      case ChallengeReason.ApplicationUnlock:
        showForgotPasscodeLink = true;
        this.cancelable = false;
        break;
      case ChallengeReason.Migration:
        showForgotPasscodeLink = true;
        this.cancelable = false;
        break;
      case ChallengeReason.ProtocolUpgrade:
        showForgotPasscodeLink = false;
        this.cancelable = true;
        break;
      case ChallengeReason.ResaveRootKey:
        showForgotPasscodeLink = false;
        this.cancelable = true;
        break;
    }
    this.cancelable = !showForgotPasscodeLink
    this.setState({
      prompts,
      values,
      processing: false,
      forgotPasscode: false,
      showForgotPasscodeLink,
      hasAccount: this.application.hasAccount(),
      title: this.challenge.title,
      subtitle: this.challenge.subtitle,
      processingPrompts: []
    });
    this.application.addChallengeObserver(
      this.challenge,
      {
        onValidValue: (value) => {
          this.getState().values[value.prompt.id]!.invalid = false;
          removeFromArray(this.state.processingPrompts, value.prompt);
          this.reloadProcessingStatus();
        },
        onInvalidValue: (value) => {
          this.getState().values[value.prompt.id]!.invalid = true;
          /** If custom validation, treat all values together and not individually */
          if (!value.prompt.validates) {
            this.setState({ processingPrompts: [], processing: false });
          } else {
            removeFromArray(this.state.processingPrompts, value.prompt);
            this.reloadProcessingStatus();
          }
        },
        onComplete: () => {
          this.dismiss();
        },
        onCancel: () => {
          this.dismiss();
        },
      }
    );
  }

  deinit() {
    (this.application as any) = undefined;
    (this.challenge as any) = undefined;
    super.deinit();
  }

  reloadProcessingStatus() {
    return this.setState({
      processing: this.state.processingPrompts.length > 0
    });
  }

  get modalTitle(): string {
    if (this.challenge.reason === ChallengeReason.Migration) {
      return STRING_STORAGE_UPDATE;
    } else {
      return STRING_AUTHENTICATION_REQUIRED;
    }
  }

  async destroyLocalData() {
    if (await confirmDialog({
      text: STRING_SIGN_OUT_CONFIRMATION,
      confirmButtonStyle: "danger"
    })) {
      await this.application.signOut();
      this.dismiss();
    };
  }

  /** @template */
  cancel() {
    if (this.cancelable) {
      this.application!.cancelChallenge(this.challenge);
    }
  }

  onForgotPasscodeClick() {
    this.setState({
      forgotPasscode: true
    });
  }

  onTextValueChange(prompt: ChallengePrompt) {
    const values = this.getState().values;
    values[prompt.id]!.invalid = false;
    this.setState({ values });
  }

  validate() {
    const failed = [];
    for (const prompt of this.getState().prompts) {
      const value = this.getState().values[prompt.id];
      if (!value || value.value.length === 0) {
        this.getState().values[prompt.id]!.invalid = true;
      }
    }
    return failed.length === 0;
  }

  async submit() {
    if (!this.validate()) {
      return;
    }
    await this.setState({ processing: true });
    const values: ChallengeValue[] = [];
    for (const inputValue of Object.values(this.getState().values)) {
      const rawValue = inputValue!!.value;
{'fraction_non_alphanumeric': 0.11832098765432099, 'fraction_numerical': 0.0005925925925925926, 'mean_word_length': 2.6902332361516037, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4043373', 'n_tokens_mistral': 3072, 'n_tokens_neox': 2967, 'n_words': 599}