<commit_before>import React, { Component } from 'react'
import 'styled-components/macro'

import './GuildConfig.scss'
import API from '../api'
import ShrinkableText from '../components/ShrinkableText'
import Notice from '../components/Notice'
import Button from '../components/Button'
import GuildIcon from '../components/GuildIcon'
import ConfigEditor from '../components/ConfigEditor'

export default class GuildConfig extends Component {
  state = {
    guild: null,
    error: null,
    saved: null,
    config: '',
  }

  get guildId() {
    return this.props.match.params.id
  }

  async componentDidMount() {
    try {
      var guild = await API.get(`/api/guild/${this.guildId}`)
      var { config } = await API.get(`/api/guild/${this.guildId}/config`)
    } catch (error) {
      this.setState({ error })
      return
    }

    this.setState({ guild, config: config || '' })
  }

  handleConfigChange = (config) => {
    this.setState({ config })
  }

  handleSaveClick = async () => {
    try {
      await API.patch(`/api/guild/${this.guildId}/config`, {
        body: this.state.config,
      })
    } catch (error) {
      const { message } = error
      this.setState({ error: message })
      return
    }

    this.setState({ saved: true, error: null })
  }

  render() {
    const { guild, config, error, saved } = this.state

    if (error != null && guild == null) {
      return <Notice mood="danger">{error.toString()}</Notice>
    }

    if (guild == null) {
      return <p>Loading...</p>
    }

    return (
      <div className="guild-detail">
        <h2>
          <GuildIcon guild={guild} />
          <ShrinkableText>{guild.name}</ShrinkableText>
        </h2>

        {error != null ? (
          <Notice mood="danger">{error.toString()}</Notice>
        ) : null}

        {saved != null ? <Notice mood="success">Saved.</Notice> : null}

        <div className="guild-config">
          <ConfigEditor
            value={config}
            onChange={this.handleConfigChange}
            fontSize={18}
            showPrintMargin={false}
            editorProps={{ $blockScrolling: true }}
            setOptions={{ tabSize: 2, showFoldWidgets: false }}
          />

          <Button onClick={this.handleSaveClick} css="margin-top: 1rem">
            Save
          </Button>
        </div>
      </div>
    )
  }
}<commit_msg>Add keyboard shortcut to save guild config<commit_after>import React, { Component } from 'react'
import 'styled-components/macro'

import './GuildConfig.scss'
import API from '../api'
import ShrinkableText from '../components/ShrinkableText'
import Notice from '../components/Notice'
import Button from '../components/Button'
import GuildIcon from '../components/GuildIcon'
import ConfigEditor from '../components/ConfigEditor'

function isMac() {
  return navigator.userAgent.includes('Macintosh')
}

export default class GuildConfig extends Component {
  state = {
    guild: null,
    error: null,
    saved: null,
    config: '',
  }

  get guildId() {
    return this.props.match.params.id
  }

  async componentDidMount() {
    try {
      var guild = await API.get(`/api/guild/${this.guildId}`)
      var { config } = await API.get(`/api/guild/${this.guildId}/config`)
    } catch (error) {
      this.setState({ error })
      return
    }

    this.setState({ guild, config: config || '' })
    window.addEventListener('keydown', this.handleKeydown)
  }

  componentWillUnmount() {
    window.removeEventListener('keydown', this.handleKeydown)
  }

  handleKeydown = (event) => {
    const modifierHeld = isMac() ? event.metaKey : event.ctrlKey
    if (modifierHeld && event.key === 's') {
      this.save()
      event.preventDefault()
    }
  }

  handleConfigChange = (config) => {
    this.setState({ config })
  }

  handleSaveClick = () => {
    this.save()
  }

  async save() {
    try {
      await API.patch(`/api/guild/${this.guildId}/config`, {
        body: this.state.config,
      })
    } catch (error) {
      const { message } = error
      this.setState({ error: message })
      return
    }

    this.setState({ saved: true, error: null })
  }

  render() {
    const { guild, config, error, saved } = this.state

    if (error != null && guild == null) {
      return <Notice mood="danger">{error.toString()}</Notice>
    }

    if (guild == null) {
      return <p>Loading...</p>
    }

    return (
      <div className="guild-detail">
        <h2>
          <GuildIcon guild={guild} />
          <ShrinkableText>{guild.name}</ShrinkableText>
        </h2>

        {error != null ? (
          <Notice mood="danger">{error.toString()}</Notice>
        ) : null}

        {saved != null ? <Notice mood="success">Saved.</Notice> : null}

        <div className="guild-config">
          <ConfigEditor
            value={config}
            onChange={this.handleConfigChange}
            fontSize={18}
            showPrintMargin={false}
            editorProps={{ $blockScrolling: true }}
            setOptions={{ tabSize: 2, showFoldWidgets: false }}
          />

          <Button onClick={this.handleSaveClick} css="margin-top: 1rem">
            Save
          </Button>

          <small css="display: block; margin-top: 1rem; opacity: 0.5;">
            You can also press {isMac() ? '⌘' : 'CTRL+'}S to save.
          </small>
        </div>
      </div>
    )
  }
}
{'fraction_non_alphanumeric': 0.15198360961072827, 'fraction_numerical': 0.002793816353138387, 'mean_word_length': 2.302583025830258, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 50, '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': '4095070', 'n_tokens_mistral': 1690, 'n_tokens_neox': 1637, 'n_words': 361}