ACWing One Theme

make acwing coding UI look more like leetcode

// ==UserScript==
// @name         ACWing One Theme
// @name:zh-CN  ACWing One Theme
// @namespace    https://whalien.space
// @author       whalien
// @version      0.2
// @description  make acwing coding UI look more like leetcode
// @description:zh-CN 让acwing编程界面看起来更像leetcode
// @icon         https://cdn.acwing.com/static/web/img/favicon.ico
// @match        https://www.acwing.com/problem/content/*
// @run-at document-end
// @grant        GM_addStyle
// @grant        GM_log
// @license      WTFPL
// ==/UserScript==

const customCSS = `
/*---------- global settings ----------*/
.file-explorer-main-field-item.file-explorer-main-field-item-desktop {
  display: none;
}

.base_body {
  padding-top: 72px !important;
}

.base_body > .container {
  width: 98%;
}

.container > .panel.panel-default > .panel-body {
  height: calc(100vh - 72px - 20px);
  padding: 10px 15px;
}

.container > .panel > .panel-body > .row {
  height: 100%;
  margin-top: 8px;
}

.base_body .row .problem-content-container {
  max-height: calc(100% - 100px);
  overflow-y: auto;
}

.container > .panel .panel-body .problem-content-title {
  font-size: 24px;
  margin: 5px 0 10px 0;
}

.base_body .row .code-editor-container {
  height: calc(100% - 110px);
  max-height: 100%;
  overflow-y: auto;
  width: calc(50% - 24px);
}
/*---------- end of global settings ----------*/

/*---------- navigation settings ----------*/
.container > .panel .nav.nav-tabs .problem-content-sub-btn{
  font-size: 1.5rem;
}

.container > .panel .nav.nav-tabs .problem-content-sub-btn .glyphicon {
  top: 2px !important;
}
/*----------- end of navigation settings ----------*/

/*----------- remove br before code editor ----------*/
.container > .panel .panel-body .nav-tabs.nav + br {
  display: none;
}

/*----------- code editor settings toolset ----------*/
.base_body .code-editor-container #code_tool_bar {
  height: 48px;
}

.base_body #code_tool_bar #open_ac_saber_btn{
  top: 9px !important;
}

.base_body #code_tool_bar .btn {
  padding-top: 0;
}

.base_body #code_tool_bar .btn .editor_tool_btn {
  padding-right: 10px;
}

.base_body #code_tool_bar .code_editor_option_language {
  margin-top: 3px !important;
}
/*----------- end of code editor settings toolset ----------*/

/*----------- submit controls ----------*/
#code_editor + div {
  position: fixed;
  bottom: 2px;
  right: 200px;
  z-index: 10000000;
}

/*
#code_editor + div .btn {
  padding: 4px 8px;
}
*/

#data-augmentation-div {
  padding-top: 27px !important;
  padding-right: 12px !important;
  color: white !important;
}
/*----------- end of submit controls ----------*/

/*----------- evaluation block ----------*/
#submit-code-status-block {
  margin-top: 25px !important;
}

#run-code-status-block {
  margin-top: 25px !important;
}
/*----------- end of evaluation block ----------*/

/*----------- dragable wedge ----------*/
.base_body .dragable-wedge {
  float: left;
/*  display: inline-block; */
  width: 10px;
  height: calc(100% - 94px);
}

.base_body .dragable-wedge:hover {
  background-color: rgb(0, 122, 255);
  cursor: col-resize;
}
/*----------- end of dragable wedge ----------*/
`

const throttle = (fn, delay) => {
    let timer = null
    return (...args) => {
        if(timer) return
        timer = setTimeout(() => {
            clearTimeout(timer)
            timer = null
        }, delay)
        fn(...args)
    }
}

function adjustLayout() {
    const problemContainer = document.querySelector('.panel-body .row')

    // make probleDiv take half the width
    const problemDiv = document.querySelector('.panel-body .row .col-sm-9.col-xs-12')
    problemDiv.className = 'problem-content-container col-sm-6 col-xs-12'
    // make infoDiv take the same width as problemDiv
    const infoDiv = document.querySelector('.panel-body .row .col-sm-3.hidden-xs')
    infoDiv.className=''
    problemDiv.appendChild(infoDiv)

    // move code editor to the same row as problem content
    const fragment = document.createDocumentFragment()
    const codeEditorDiv = document.createElement('div')
    codeEditorDiv.className = 'code-editor-container col-sm-5'
    const codeEditorFrag = document.querySelectorAll('.panel-body > .row ~ div')
    codeEditorFrag.forEach((editorFrag) => {
        codeEditorDiv.appendChild(editorFrag)
    })
    fragment.appendChild(codeEditorDiv)
    problemContainer.appendChild(fragment)

    // add dragable resizer
    const dragableDiv = document.createElement('div')
    dragableDiv.className = 'dragable-wedge'
    problemContainer.insertBefore(dragableDiv, codeEditorDiv)

    // get the last container width and set
    const lastProblemWidth = localStorage.getItem('problemWidth')
    const lastCodeEditorWidth = localStorage.getItem('codeEditorWidth')
    if(lastProblemWidth!==null && lastCodeEditorWidth!==null) {
        problemDiv.style.width = lastProblemWidth
        codeEditorDiv.style.width = lastCodeEditorWidth
    }
}

let lastX = -1
function enableDragAndDrop() {
    const problemContent = document.querySelector('.base_body .problem-content-container')
    const codeEditor = document.querySelector('.base_body .code-editor-container')
    const resizer = document.querySelector('.base_body .dragable-wedge')
    const dragEventHandler = (e) => {
        const dx = lastX == -1 ? 0:e.clientX - lastX
        lastX = e.clientX
        if(dx > 100 || dx == 0) return

        let problemContentWidth = problemContent.getBoundingClientRect().width + dx
        let codeEditorWidth = codeEditor.getBoundingClientRect().width - dx
        if(problemContentWidth < 0 || codeEditorWidth < 0) {
            return
        }

        problemContentWidth = `${problemContentWidth}px`
        codeEditorWidth = `${codeEditorWidth}px`
        problemContent.style.width = problemContentWidth
        codeEditor.style.width = codeEditorWidth
        localStorage.setItem('problemWidth', problemContentWidth)
        localStorage.setItem('codeEditorWidth', codeEditorWidth)
    }
    resizer.addEventListener('drag', throttle(dragEventHandler, 60))
}

function bindSubmitScrollBehavior() {
    const codeEditorContainer = document.querySelector('.base_body .code-editor-container')
    const submitBtn = document.querySelector('#submit_code_btn')
    const runCodeBtn = document.querySelector('#run_code_btn')
    // FIXME(whalien): figure out why we cannot delegate click event to the outer div
    submitBtn.addEventListener('click', (e) => {
        setTimeout(()=> {
            codeEditorContainer.scrollTo({
                left: 0,
                top: codeEditorContainer.scrollHeight,
                behavior: 'smooth'
            })
        }, 1000)
    })
    runCodeBtn.addEventListener('click', (e) => {
        setTimeout(()=> {
            codeEditorContainer.scrollTo({
                left: 0,
                top: codeEditorContainer.scrollHeight,
                behavior: 'smooth'
            })
        }, 1000)
    });
}

(function() {
    'use strict';
    adjustLayout()

    GM_addStyle(customCSS)

    enableDragAndDrop()

    bindSubmitScrollBehavior()
})();