<commit_before>import React, { Component } from 'react';
import { BrowserRouter as Router, Link, NavLink, Switch, Route, Redirect } from 'react-router-dom';

import './App.css';

import WidthWrapper from './WidthWrapper.js';
import Player from './Player.js';
import Settings from './Settings.js';
import AddCollection from './AddCollection.js';
import ImportEpwing from './ImportEpwing.js';

class ScrollToTopOnMount extends Component {
  componentDidMount() {
    window.scrollTo(0, 0)
  }

  render() {
    return null
  }
}

const VideoListItem = (props) => {
  const { videoId, collection, name } = props;
  const hasSubs = collection.videos.get(videoId).subtitleTracks.size > 0;

  return (
    <li className={'App-library-list-item ' + (hasSubs ? 'App-library-list-item-has-subs' : 'App-library-list-item-no-subs')}>
      <Link to={'/player/' + encodeURIComponent(collection.locator) + '/' + encodeURIComponent(videoId)}>
        {name}
      </Link>
    </li>
  );
};

// App
class App extends Component {
  render() {
    const { mainState, actions } = this.props;

    if (mainState.modalLoadingMessage) {
      return <WidthWrapper><h1 className="header-font">{mainState.modalLoadingMessage}</h1></WidthWrapper>;
    } else {
      return (
        <Router>
          <Switch>
            <Route path="/player/:cloc/:vid" render={({ match, history }) => {
              const collectionLocator = decodeURIComponent(match.params.cloc);
              const videoId = decodeURIComponent(match.params.vid);
              return <Player video={mainState.collections.get(collectionLocator).videos.get(videoId)} onExit={() => { history.goBack(); }} onUpdatePlaybackPosition={(pos) => { actions.saveVideoPlaybackPosition(collectionLocator, videoId, pos); }} onNeedSubtitles={() => { actions.loadSubtitlesIfNeeded(collectionLocator, videoId); }} getSavedPlaybackPosition={() => { return actions.loadVideoPlaybackPosition(collectionLocator, videoId); }} onSetPreference={(pref, value) => { actions.setPreference(pref, value); }} preferences={mainState.preferences} sortSubtitleTracksMap={actions.sortSubtitleTracksMap} searchDictionaries={actions.searchDictionaries} />;
            }}/>
            <Route path="/add_collection" render={({ history }) => {
              return <AddCollection onAdd={(name, dir) => { actions.addLocalCollection(name, dir); history.replace('/library'); }} onExit={() => { history.goBack(); }} />;
            }}/>
            <Route path="/import_epwing" render={({ history }) => {
              return <ImportEpwing onExit={() => { history.goBack(); }} onReloadDictionaries={actions.reloadDictionaries} />;
            }}/>
            <Route render={() => (
              <WidthWrapper>
                <nav className="App-main-nav header-font">
                  <NavLink to={'/library'} activeClassName="selected">Library</NavLink>
                  <NavLink to={'/settings'} activeClassName="selected">Settings</NavLink>
                </nav>
                <div className="App-below-main-nav">
                  <Switch>
                    <Route path="/library/:cloc/:tname" render={({ match }) => {
                      const collectionLocator = decodeURIComponent(match.params.cloc);
                      const titleName = decodeURIComponent(match.params.tname);
                      const collection = mainState.collections.get(collectionLocator);
                      const title = collection.titles.find(t => t.name === titleName); // unindexed, but should be quick
                      return (
                        <div>
                          <ScrollToTopOnMount/>
                          <div className="App-collection-header">
                            <h2 className="App-collection-header-title"><Link to="/library" className="App-back-to-library-link">{collection.name}</Link> / {title.name}</h2>
                          </div>
                          {title.parts.episodes.length ? (
                            <ul>
                              {title.parts.episodes.map(ep => (
                                <VideoListItem collection={collection} videoId={ep.videoId} name={'Episode ' + ep.number} key={ep.number} />
                              ))}
                            </ul>
                          ) : null}
                          {title.parts.others.length ? (
                            <ul>
                              {title.parts.others.map(other => (
                                <VideoListItem collection={collection} videoId={other.videoId} name={other.name} key={other.name} />
                              ))}
                            </ul>
                          ) : null}
                        </div>
                      );
                    }}/>
                    <Route path="/library" render={() => (mainState.collections.size > 0) ? (
                      <ul>
                        {mainState.collections.valueSeq().sort((a, b) => a.name.localeCompare(b.name)).map((collection) => (
                          <li className="App-collection" key={collection.locator}>
                            <div className="App-collection-header">
                              <h2 className="App-collection-header-title">{collection.name} <span className="App-collection-header-buttons">
                                <button onClick={e => {
                                  e.preventDefault();
                                  if (window.confirm('Are you sure you want to delete the collection "' + collection.name + '"?')) {
                                    actions.removeCollection(collection.locator);
                                  }
                                }}>Delete</button>{' '}
                              </span></h2>
                              <div className="App-collection-id">{collection.locator}</div><commit_msg>Fix uncommon issue with react key uniqueness<commit_after>import React, { Component } from 'react';
import { BrowserRouter as Router, Link, NavLink, Switch, Route, Redirect } from 'react-router-dom';

import './App.css';

import WidthWrapper from './WidthWrapper.js';
import Player from './Player.js';
import Settings from './Settings.js';
import AddCollection from './AddCollection.js';
import ImportEpwing from './ImportEpwing.js';

class ScrollToTopOnMount extends Component {
  componentDidMount() {
    window.scrollTo(0, 0)
  }

  render() {
    return null
  }
}

const VideoListItem = (props) => {
  const { videoId, collection, name } = props;
  const hasSubs = collection.videos.get(videoId).subtitleTracks.size > 0;

  return (
    <li className={'App-library-list-item ' + (hasSubs ? 'App-library-list-item-has-subs' : 'App-library-list-item-no-subs')}>
      <Link to={'/player/' + encodeURIComponent(collection.locator) + '/' + encodeURIComponent(videoId)}>
        {name}
      </Link>
    </li>
  );
};

// App
class App extends Component {
  render() {
    const { mainState, actions } = this.props;

    if (mainState.modalLoadingMessage) {
      return <WidthWrapper><h1 className="header-font">{mainState.modalLoadingMessage}</h1></WidthWrapper>;
    } else {
      return (
        <Router>
          <Switch>
            <Route path="/player/:cloc/:vid" render={({ match, history }) => {
              const collectionLocator = decodeURIComponent(match.params.cloc);
              const videoId = decodeURIComponent(match.params.vid);
              return <Player video={mainState.collections.get(collectionLocator).videos.get(videoId)} onExit={() => { history.goBack(); }} onUpdatePlaybackPosition={(pos) => { actions.saveVideoPlaybackPosition(collectionLocator, videoId, pos); }} onNeedSubtitles={() => { actions.loadSubtitlesIfNeeded(collectionLocator, videoId); }} getSavedPlaybackPosition={() => { return actions.loadVideoPlaybackPosition(collectionLocator, videoId); }} onSetPreference={(pref, value) => { actions.setPreference(pref, value); }} preferences={mainState.preferences} sortSubtitleTracksMap={actions.sortSubtitleTracksMap} searchDictionaries={actions.searchDictionaries} />;
            }}/>
            <Route path="/add_collection" render={({ history }) => {
              return <AddCollection onAdd={(name, dir) => { actions.addLocalCollection(name, dir); history.replace('/library'); }} onExit={() => { history.goBack(); }} />;
            }}/>
            <Route path="/import_epwing" render={({ history }) => {
              return <ImportEpwing onExit={() => { history.goBack(); }} onReloadDictionaries={actions.reloadDictionaries} />;
            }}/>
            <Route render={() => (
              <WidthWrapper>
                <nav className="App-main-nav header-font">
                  <NavLink to={'/library'} activeClassName="selected">Library</NavLink>
                  <NavLink to={'/settings'} activeClassName="selected">Settings</NavLink>
                </nav>
                <div className="App-below-main-nav">
                  <Switch>
                    <Route path="/library/:cloc/:tname" render={({ match }) => {
                      const collectionLocator = decodeURIComponent(match.params.cloc);
                      const titleName = decodeURIComponent(match.params.tname);
                      const collection = mainState.collections.get(collectionLocator);
                      const title = collection.titles.find(t => t.name === titleName); // unindexed, but should be quick
                      return (
                        <div>
                          <ScrollToTopOnMount/>
                          <div className="App-collection-header">
                            <h2 className="App-collection-header-title"><Link to="/library" className="App-back-to-library-link">{collection.name}</Link> / {title.name}</h2>
                          </div>
                          {title.parts.episodes.length ? (
                            <ul>
                              {title.parts.episodes.map(ep => (
                                <VideoListItem collection={collection} videoId={ep.videoId} name={'Episode ' + ep.number} key={ep.videoId} />
                              ))}
                            </ul>
                          ) : null}
                          {title.parts.others.length ? (
                            <ul>
                              {title.parts.others.map(other => (
                                <VideoListItem collection={collection} videoId={other.videoId} name={other.name} key={other.name} />
                              ))}
                            </ul>
                          ) : null}
                        </div>
                      );
                    }}/>
                    <Route path="/library" render={() => (mainState.collections.size > 0) ? (
                      <ul>
                        {mainState.collections.valueSeq().sort((a, b) => a.name.localeCompare(b.name)).map((collection) => (
                          <li className="App-collection" key={collection.locator}>
                            <div className="App-collection-header">
                              <h2 className="App-collection-header-title">{collection.name} <span className="App-collection-header-buttons">
                                <button onClick={e => {
                                  e.preventDefault();
                                  if (window.confirm('Are you sure you want to delete the collection "' + collection.name + '"?')) {
                                    actions.removeCollection(collection.locator);
                                  }
                                }}>Delete</button>{' '}
                              </span></h2>
                              <div className="App-collection-id">{collection.locator}</div>
{'fraction_non_alphanumeric': 0.1398743846545578, 'fraction_numerical': 0.0016975046681378374, 'mean_word_length': 1.8249820187005514, 'pattern_counts': {'":': 0, '<': 111, '<?xml version=': 0, '>': 153, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3999328', 'n_tokens_mistral': 3029, 'n_tokens_neox': 2988, 'n_words': 519}