<commit_before>import AsciinemaPlayerCore from '../core';
import { batch, createEffect, createMemo, Match, onCleanup, onMount, Switch } from 'solid-js';
import { createStore, reconcile } from 'solid-js/store';
import Terminal from './Terminal';
import ControlBar from './ControlBar';
import LoaderOverlay from './LoaderOverlay';
import StartOverlay from './StartOverlay';


export default props => {
  const [state, setState] = createStore({
    state: 'initial',
    cols: props.cols,
    rows: props.rows,
    lines: [],
    cursor: undefined,
    charW: null,
    charH: null,
    bordersW: null,
    bordersH: null,
    containerW: null,
    containerH: null,
    showControls: false,
    currentTime: null,
    remainingTime: null,
    progress: null,
    blink: true,
    cursorHold: false
  });

  let frameRequestId;
  let userActivityTimeoutId;
  let timeUpdateIntervalId;
  let blinkIntervalId;

  let wrapperRef;
  let terminalRef;

  let resizeObserver;

  const terminalCols = () => state.cols || 80;
  const terminalRows = () => state.rows || 24;

  const core = AsciinemaPlayerCore.build(props.src, {
    cols: props.cols,
    rows: props.rows,
    loop: props.loop,
    speed: props.speed,

    onSize: (cols, rows) => {
      if (!state.cols) {
        setState({ cols, rows });
      }
    },

    onTerminalUpdate: () => {
      if (frameRequestId === undefined) {
        frameRequestId = requestAnimationFrame(updateTerminal);
      }
    },

    onFinish: () => {
      setState('state', 'paused');
    }
  });

  core.init();

  onMount(async () => {
    console.debug('player mounted');

    if (props.preload) {
      await core.preload();
      updateTime();
    }

    setState({
      charW: terminalRef.clientWidth / terminalCols(),
      charH: terminalRef.clientHeight / terminalRows(),
      bordersW: terminalRef.offsetWidth - terminalRef.clientWidth,
      bordersH: terminalRef.offsetHeight - terminalRef.clientHeight,
      containerW: wrapperRef.offsetWidth,
      containerH: wrapperRef.offsetHeight
    });

    resizeObserver = new ResizeObserver(_entries => {
      setState({
        containerW: wrapperRef.offsetWidth,
        containerH: wrapperRef.offsetHeight
      });
    });

    resizeObserver.observe(wrapperRef);

    if (props.autoplay) {
      play();
    }
  });

  onCleanup(() => {
    core.stop()
    stopBlinking();
    stopTimeUpdates();
    resizeObserver.disconnect();
  });

  createEffect(() => {
    state.cursor;  // <- accessing this subscribes this effect for cursor change
    setState('cursorHold', true);
  });

  createEffect(() => {
    const s = state.state;

    if (s === 'playing') {
      startBlinking();
      startTimeUpdates();
    } else if (s === 'paused') {
      stopBlinking();
      stopTimeUpdates();
      updateTime();
    }
  });

  const play = async () => {
    setState('state', 'loading');

    const timeoutId = setTimeout(() => {
      setState('state', 'waiting');
    }, 1000);

    await core.play();
    clearTimeout(timeoutId);
    setState('state', 'playing');
  }

  const pauseOrResume = async () => {
    const isPlaying = await core.pauseOrResume();
    setState('state', isPlaying ? 'playing' : 'paused');
  }

  const seek = async pos => {
    if (await core.seek(pos)) {
      updateTime();
    }
  }

  const updateTerminal = () => {
    const cursor = core.getCursor();
    const changedLines = core.getChangedLines();

    batch(() => {
      setState('cursor', reconcile(cursor));

      if (changedLines) {
        changedLines.forEach((line, i) => {
          setState('lines', i, reconcile(line));
        });

        setState('cursorHold', true);
      }
    });
<commit_msg>Simplify cursor hold implementation<commit_after>import AsciinemaPlayerCore from '../core';
import { createEffect, createMemo, Match, onCleanup, onMount, Switch } from 'solid-js';
import { createStore, reconcile } from 'solid-js/store';
import Terminal from './Terminal';
import ControlBar from './ControlBar';
import LoaderOverlay from './LoaderOverlay';
import StartOverlay from './StartOverlay';


export default props => {
  const [state, setState] = createStore({
    state: 'initial',
    cols: props.cols,
    rows: props.rows,
    lines: [],
    cursor: undefined,
    charW: null,
    charH: null,
    bordersW: null,
    bordersH: null,
    containerW: null,
    containerH: null,
    showControls: false,
    currentTime: null,
    remainingTime: null,
    progress: null,
    blink: true,
    cursorHold: false
  });

  let frameRequestId;
  let userActivityTimeoutId;
  let timeUpdateIntervalId;
  let blinkIntervalId;

  let wrapperRef;
  let terminalRef;

  let resizeObserver;

  const terminalCols = () => state.cols || 80;
  const terminalRows = () => state.rows || 24;

  const core = AsciinemaPlayerCore.build(props.src, {
    cols: props.cols,
    rows: props.rows,
    loop: props.loop,
    speed: props.speed,

    onSize: (cols, rows) => {
      if (!state.cols) {
        setState({ cols, rows });
      }
    },

    onTerminalUpdate: () => {
      if (frameRequestId === undefined) {
        frameRequestId = requestAnimationFrame(updateTerminal);
      }
    },

    onFinish: () => {
      setState('state', 'paused');
    }
  });

  core.init();

  onMount(async () => {
    console.debug('player mounted');

    if (props.preload) {
      await core.preload();
      updateTime();
    }

    setState({
      charW: terminalRef.clientWidth / terminalCols(),
      charH: terminalRef.clientHeight / terminalRows(),
      bordersW: terminalRef.offsetWidth - terminalRef.clientWidth,
      bordersH: terminalRef.offsetHeight - terminalRef.clientHeight,
      containerW: wrapperRef.offsetWidth,
      containerH: wrapperRef.offsetHeight
    });

    resizeObserver = new ResizeObserver(_entries => {
      setState({
        containerW: wrapperRef.offsetWidth,
        containerH: wrapperRef.offsetHeight
      });
    });

    resizeObserver.observe(wrapperRef);

    if (props.autoplay) {
      play();
    }
  });

  onCleanup(() => {
    core.stop()
    stopBlinking();
    stopTimeUpdates();
    resizeObserver.disconnect();
  });

  createEffect(() => {
    const s = state.state;

    if (s === 'playing') {
      startBlinking();
      startTimeUpdates();
    } else if (s === 'paused') {
      stopBlinking();
      stopTimeUpdates();
      updateTime();
    }
  });

  const play = async () => {
    setState('state', 'loading');

    const timeoutId = setTimeout(() => {
      setState('state', 'waiting');
    }, 1000);

    await core.play();
    clearTimeout(timeoutId);
    setState('state', 'playing');
  }

  const pauseOrResume = async () => {
    const isPlaying = await core.pauseOrResume();
    setState('state', isPlaying ? 'playing' : 'paused');
  }

  const seek = async pos => {
    if (await core.seek(pos)) {
      updateTime();
    }
  }

  const updateTerminal = () => {
    const changedLines = core.getChangedLines();

    if (changedLines) {
      changedLines.forEach((line, i) => {
        setState('lines', i, reconcile(line));
      });
    }

    setState('cursor', reconcile(core.getCursor()));
    setState('cursorHold', true);

{'fraction_non_alphanumeric': 0.14367176634214188, 'fraction_numerical': 0.0022253129346314327, 'mean_word_length': 2.8229665071770333, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 37, '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': '649129', 'n_tokens_mistral': 2264, 'n_tokens_neox': 2165, 'n_words': 488}