Дополнительные инструменты для NetSchool / NetCity (Сетевой Город. Образование)
目前為
// ==UserScript==
// @name NetSchool Tweaks
// @namespace https://greasyfork.org/users/843419
// @description Дополнительные инструменты для NetSchool / NetCity (Сетевой Город. Образование)
// @version 1.0.4
// @author Zgoly
// @match *://*/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=ir-tech.ru
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
try {
console.log(language.Generic.Calendar.kTitle1, 'найден, NetSchool Tweaks активен')
} catch {
return
}
let autoLogin = GM_getValue('autoLogin', false)
let loginName = GM_getValue('loginName', 'Пользователь')
let password = GM_getValue('password', '12345678')
let schoolId = GM_getValue('schoolId', '')
let autoSkip = GM_getValue('autoSkip', true)
function waitForElement(selector) {
return new Promise(resolve => {
if (document.querySelector(selector)) return resolve(document.querySelector(selector))
const observer = new MutationObserver(mutations => {
if (document.querySelector(selector)) {
observer.disconnect()
resolve(document.querySelector(selector))
}
})
observer.observe(document.body, { childList: true, subtree: true })
})
}
if (autoLogin && window.location.pathname.startsWith('/authorize')) {
function runAngularAction() {
try {
angular.element(document.body).scope().$$childTail.$ctrl.loginStrategiesService.loginWithLoginPassCheck(loginName, password, schoolId, null, { 'idpBindUser': 1 })
} catch {
requestAnimationFrame(runAngularAction)
}
}
runAngularAction()
}
waitForElement('ns-modal').then((element) => {
if (autoSkip && element.getAttribute('header') == language.Generic.Login.kTitleSecurityWarning) {
element.querySelector('button').click()
console.log('Security warning modal skipped')
}
})
function nstSwitch(parentElement) {
let label = document.createElement('label')
label.classList.add('nst-switch')
let input = document.createElement('input')
input.type = 'checkbox'
input.classList.add('nst-hide')
let div = document.createElement('div')
label.append(input)
label.append(div)
parentElement.append(label)
return input
}
function nstModal(headlineText, contentHTML, showSaveButton = true) {
return new Promise((resolve, reject) => {
let dialog = document.createElement('dialog')
dialog.classList.add('nst-dialog')
let dialogWrapper = document.createElement('div')
dialogWrapper.classList.add('nst-dialog-wrapper')
dialog.append(dialogWrapper)
let headline = document.createElement('p')
headline.classList.add('nst-headline')
headline.textContent = headlineText
dialogWrapper.append(headline)
let dialogAutofocus = document.createElement('input')
dialogAutofocus.autofocus = 'autofocus'
dialogAutofocus.style.display = 'none'
dialogWrapper.append(dialogAutofocus)
let content = document.createElement('div')
content.classList.add('nst-content')
content.append(contentHTML)
dialogWrapper.append(content)
let actions = document.createElement('div')
actions.classList.add('nst-actions')
let closeButton = document.createElement('button')
closeButton.classList.add('nst-close')
closeButton.textContent = 'Закрыть'
closeButton.addEventListener('click', () => closeDialog(false))
actions.append(closeButton)
if (showSaveButton) {
let saveButton = document.createElement('button')
saveButton.classList.add('nst-save')
saveButton.textContent = 'Сохранить'
saveButton.addEventListener('click', () => closeDialog(true))
actions.append(saveButton)
}
dialogWrapper.append(actions)
document.body.append(dialog)
dialog.showModal()
// Убираем фокус с поля ввода
document.activeElement.blur()
document.body.classList.add('nst-no-scroll')
function closeDialog(result = false) {
dialog.classList.add('nst-hide-dialog')
setTimeout(() => {
dialog.remove()
if (document.getElementsByTagName('dialog').length < 1) document.body.classList.remove('nst-no-scroll')
}, 500)
resolve(result)
}
contentHTML.closeDialog = closeDialog
dialog.addEventListener('click', (event) => {
if (event.target === dialog) {
closeDialog(false)
}
})
dialog.addEventListener('close', () => closeDialog())
dialog.addEventListener('error', reject)
})
}
let settings = document.createElement('li')
let settingsLink = document.createElement('a')
settings.append(settingsLink)
let settingsBody = document.createElement('span')
settingsBody.classList.add('cb-settings')
settingsLink.append(settingsBody)
let settingsIcon = document.createElement('i')
settingsIcon.classList.add('icon-gear', 'nst-settings-icon')
settingsBody.append(settingsIcon)
settingsBody.addEventListener('click', () => {
let div = document.createElement('div')
let table = document.createElement('table')
// Переключатель авто входа
let autoLoginRow = document.createElement('tr')
let autoLoginLabelCell = document.createElement('td')
let autoLoginLabelTitle = document.createElement('div')
autoLoginLabelTitle.classList.add('nst-label-title')
autoLoginLabelTitle.textContent = 'Авто вход'
autoLoginLabelCell.append(autoLoginLabelTitle)
let autoLoginLabelDescription = document.createElement('div')
autoLoginLabelDescription.classList.add('nst-label-description')
autoLoginLabelDescription.textContent = 'Авто вход по логину и паролю.'
autoLoginLabelCell.append(autoLoginLabelDescription)
let autoLoginInputCell = document.createElement('td')
let autoLoginInput = nstSwitch(autoLoginInputCell)
autoLoginInput.checked = autoLogin
autoLoginRow.append(autoLoginLabelCell)
autoLoginRow.append(autoLoginInputCell)
table.append(autoLoginRow)
// Поле логина
let loginNameRow = document.createElement('tr')
let loginNameLabelCell = document.createElement('td')
let loginNameLabelTitle = document.createElement('div')
loginNameLabelTitle.classList.add('nst-label-title')
loginNameLabelTitle.textContent = 'Логин'
loginNameLabelCell.append(loginNameLabelTitle)
let loginNameLabelDescription = document.createElement('div')
loginNameLabelDescription.classList.add('nst-label-description')
loginNameLabelDescription.textContent = 'Логин для входа.'
loginNameLabelCell.append(loginNameLabelDescription)
let loginNameInputCell = document.createElement('td')
loginNameInputCell.classList.add('nst-flex')
let loginNameInput = document.createElement('input')
loginNameInput.type = 'text'
loginNameInput.value = loginName
loginNameInputCell.append(loginNameInput)
loginNameRow.append(loginNameLabelCell)
loginNameRow.append(loginNameInputCell)
table.append(loginNameRow)
// Поле пароля
let passwordRow = document.createElement('tr')
let passwordLabelCell = document.createElement('td')
let passwordLabelTitle = document.createElement('div')
passwordLabelTitle.classList.add('nst-label-title')
passwordLabelTitle.textContent = 'Пароль'
passwordLabelCell.append(passwordLabelTitle)
let passwordLabelDescription = document.createElement('div')
passwordLabelDescription.classList.add('nst-label-description')
passwordLabelDescription.textContent = 'Пароль для входа.'
passwordLabelCell.append(passwordLabelDescription)
let passwordInputCell = document.createElement('td')
passwordInputCell.classList.add('nst-password-input-cell', 'nst-flex')
let passwordInput = document.createElement('input')
passwordInput.type = 'password'
passwordInput.classList.add('nst-password')
passwordInput.value = password
let passwordEye = document.createElement('div')
passwordEye.classList.add('nst-password-eye')
passwordEye.addEventListener('click', () => {
passwordInput.type = passwordInput.type === 'password' ? 'text' : 'password'
})
let passwordEyeInner = document.createElement('div')
passwordEyeInner.classList.add('nst-password-eye-inner')
let passwordEyeOuter = document.createElement('div')
passwordEyeOuter.classList.add('nst-password-eye-outer')
passwordEye.append(passwordEyeInner)
passwordEye.append(passwordEyeOuter)
passwordInputCell.append(passwordInput)
passwordInputCell.append(passwordEye)
passwordRow.append(passwordLabelCell)
passwordRow.append(passwordInputCell)
table.append(passwordRow)
// Поле ID школы
let schoolIdRow = document.createElement('tr')
let schoolIdLabelCell = document.createElement('td')
let schoolIdLabelTitle = document.createElement('div')
schoolIdLabelTitle.classList.add('nst-label-title')
schoolIdLabelTitle.textContent = 'ID школы'
schoolIdLabelCell.append(schoolIdLabelTitle)
let schoolIdLabelDescription = document.createElement('div')
schoolIdLabelDescription.classList.add('nst-label-description')
schoolIdLabelDescription.textContent = 'ID школы для входа. Оставьте пустым, если не знаете.'
schoolIdLabelCell.append(schoolIdLabelDescription)
let schoolIdInputCell = document.createElement('td')
schoolIdInputCell.classList.add('nst-flex')
let schoolIdInput = document.createElement('input')
schoolIdInput.type = 'text'
schoolIdInput.value = schoolId
schoolIdInput.placeholder = schoolId
schoolIdInputCell.append(schoolIdInput)
schoolIdRow.append(schoolIdLabelCell)
schoolIdRow.append(schoolIdInputCell)
table.append(schoolIdRow)
// Переключатель авто пропуска
let autoSkipRow = document.createElement('tr')
let autoSkipLabelCell = document.createElement('td')
let autoSkipLabelTitle = document.createElement('div')
autoSkipLabelTitle.classList.add('nst-label-title')
autoSkipLabelTitle.textContent = 'Авто пропуск'
autoSkipLabelCell.append(autoSkipLabelTitle)
let autoSkipLabelDescription = document.createElement('div')
autoSkipLabelDescription.classList.add('nst-label-description')
autoSkipLabelDescription.textContent = 'Авто пропуск навязчивых уведомлений.'
autoSkipLabelCell.append(autoSkipLabelDescription)
let autoSkipInputCell = document.createElement('td')
let autoSkipInput = nstSwitch(autoSkipInputCell)
autoSkipInput.checked = autoSkip
autoSkipRow.append(autoSkipLabelCell)
autoSkipRow.append(autoSkipInputCell)
table.append(autoSkipRow)
function toggleFields() {
let fields = [loginNameInput, passwordInput, schoolIdInput]
fields.forEach(field => {
field.disabled = !autoLoginInput.checked
})
}
toggleFields()
autoLoginInput.addEventListener('change', toggleFields)
div.append(table)
// Сохранение настроек
nstModal('Настройки', div).then(save => {
if (save) {
GM_setValue('autoLogin', autoLoginInput.checked)
autoLogin = autoLoginInput.checked
GM_setValue('loginName', loginNameInput.value)
loginName = loginNameInput.value
GM_setValue('password', passwordInput.value)
password = passwordInput.value
GM_setValue('schoolId', schoolIdInput.value == '' ? appContext.schoolId : schoolIdInput.value)
schoolId = schoolIdInput.value == '' ? appContext.schoolId : schoolIdInput.value
GM_setValue('autoSkip', autoSkipInput.checked)
autoSkip = autoSkipInput.checked
}
})
let previewMarksWrapper = document.createElement('div')
previewMarksWrapper.classList.add('preview-marks-wrapper')
div.append(previewMarksWrapper)
let at = appContext.at
let weekStart = appContext.weekStart
let weekEnd = appContext.weekEnd
// Начало учебного года
let startDateInput = document.createElement('input')
startDateInput.type = 'date'
startDateInput.value = weekStart
previewMarksWrapper.append(startDateInput)
// Конец учебного года
let endDateInput = document.createElement('input')
endDateInput.type = 'date'
endDateInput.value = weekEnd
previewMarksWrapper.append(endDateInput)
if (weekStart == undefined || weekEnd == undefined) {
fetch('/webapi/v2/reports/studenttotal', { 'headers': { 'at': at } }).then((response) => {
return response.json()
}).then((data) => {
weekStart = data.filterSources[3].defaultRange.start.substring(0, 10)
startDateInput.value = weekStart
weekEnd = data.filterSources[3].defaultRange.end.substring(0, 10)
endDateInput.value = weekEnd
})
}
// Кнопка предпросмотра оценок
let previewMarksButton = document.createElement('button')
previewMarksButton.innerText = 'Предпросмотр оценок'
previewMarksButton.addEventListener('click', () => {
let marksTableWrapper = document.createElement('div')
marksTableWrapper.classList.add('nst-marks-table-wrapper')
let contentDiv = document.createElement('div')
contentDiv.classList.add('nst-content')
contentDiv.append(marksTableWrapper)
fetch('/webapi/student/diary/init', { 'headers': { 'at': at } }).then((response) => {
return response.json()
}).then((data) => {
let studentId = data.students[0].studentId
let yearId = appContext.yearId
let startDate = startDateInput.value
let endDate = endDateInput.value
// Запрос дневика
fetch(`/webapi/student/diary?studentId=${studentId}&weekStart=${startDate}&weekEnd=${endDate}&withLaAssigns=true&yearId=${yearId}`, { 'headers': { 'at': at } }).then((response) => {
return response.json()
}).then((data) => {
// Повторный запрос дневника (с текущей датой для отображения правильной недели, игнорируется)
let currentDate = date2strf(new Date(), 'yyyy\x01mm\x01dd\x01.')
fetch(`/webapi/student/diary?studentId=${studentId}&weekStart=${currentDate}&weekEnd=${currentDate}&withLaAssigns=true&yearId=${yearId}`, { 'headers': { 'at': at } })
let marksTable = document.createElement('table')
marksTableWrapper.append(marksTable)
let tableControlsDiv = document.createElement('div')
tableControlsDiv.classList.add('nst-controls')
contentDiv.append(tableControlsDiv)
let selectAllButton = document.createElement('button')
selectAllButton.innerText = 'Выбрать все'
selectAllButton.addEventListener('click', () => {
for (let row of marksTable.rows) {
row.classList.add('nst-row-selected')
}
updateButtons()
})
tableControlsDiv.append(selectAllButton)
let deselectAllButton = document.createElement('button')
deselectAllButton.innerText = 'Отменить выбор'
deselectAllButton.addEventListener('click', () => {
for (let row of marksTable.rows) {
row.classList.remove('nst-row-selected')
}
updateButtons()
})
tableControlsDiv.append(deselectAllButton)
let addRowButton = document.createElement('button')
addRowButton.innerText = 'Cоздать'
addRowButton.addEventListener('click', () => {
let selectedRows = marksTable.querySelectorAll('.nst-row-selected')
let newRow = createRow()
if (selectedRows.length > 0) {
selectedRows[selectedRows.length - 1].after(newRow)
selectedRows.forEach(row => row.classList.remove('nst-row-selected'))
} else {
marksTable.prepend(newRow)
}
cookRow(newRow)
newRow.classList.add('nst-row-selected')
newRow.scrollIntoView({behavior: "smooth"})
updateButtons()
})
tableControlsDiv.append(addRowButton)
let cloneRowButton = document.createElement('button')
cloneRowButton.innerText = 'Клонировать'
cloneRowButton.addEventListener('click', () => {
let selectedRows = marksTable.querySelectorAll('.nst-row-selected')
selectedRows.forEach(row => {
let clonedRow = row.cloneNode(true)
row.after(clonedRow)
cookRow(clonedRow)
let marksCell = clonedRow.querySelector('.nst-marks-cell')
Array.from(marksCell.children).forEach(markDiv => {
cookMark(markDiv)
})
row.classList.remove('nst-row-selected')
})
updateButtons()
})
tableControlsDiv.append(cloneRowButton)
let removeRowButton = document.createElement('button')
removeRowButton.innerText = 'Удалить'
removeRowButton.addEventListener('click', () => {
let selectedRows = marksTable.querySelectorAll('.nst-row-selected')
selectedRows.forEach(row => row.remove())
updateButtons()
})
tableControlsDiv.append(removeRowButton)
let addMarkButton = document.createElement('button')
addMarkButton.innerText = 'Добавить оценку'
addMarkButton.addEventListener('click', () => {
let selectedRows = marksTable.querySelectorAll('.nst-row-selected')
if (selectedRows.length == 0) return
let [templateTable, markInput, weightInput] = markModalTemplate(5, 20)
nstModal('Добавление оценки', templateTable).then(save => {
if (save) {
selectedRows.forEach(row => {
let mark = createMark(markInput.value, weightInput.value)
row.querySelector('.nst-marks-cell').append(mark)
cookMark(mark)
highlightMark(mark)
row.classList.remove('nst-row-selected')
})
updateButtons()
}
})
})
tableControlsDiv.append(addMarkButton)
function createMark(mark, weight, fullAssignment = null) {
let markDiv = document.createElement('div')
if (fullAssignment) markDiv.dataset.assignment = JSON.stringify(fullAssignment)
markDiv.classList.add('nst-mark')
let markValue = document.createElement('p')
markValue.innerText = mark
markValue.classList.add('nst-mark-value')
markDiv.append(markValue)
let weightValue = document.createElement('p')
weightValue.innerText = weight
weightValue.classList.add('nst-weight-value')
markDiv.append(weightValue)
return markDiv
}
function createRow(name = '') {
let row = document.createElement('tr')
let nameCell = document.createElement('td')
let nameInput = document.createElement('input')
nameInput.value = name
nameInput.classList.add('nst-name-input')
nameInput.placeholder = "Имя предмета"
nameCell.append(nameInput)
row.append(nameCell)
let marksCell = document.createElement('td')
marksCell.classList.add('nst-marks-cell')
row.append(marksCell)
let totalCell = document.createElement('td')
totalCell.classList.add('nst-total-cell')
row.append(totalCell)
return row
}
function markModalTemplate(mark, weight) {
let templateTable = document.createElement('table')
let markRow = document.createElement('tr')
templateTable.append(markRow)
let markLabelCell = document.createElement('td')
markRow.append(markLabelCell)
let markLabelTitle = document.createElement('div')
markLabelTitle.classList.add('nst-label-title')
markLabelTitle.textContent = 'Оценка'
markLabelCell.append(markLabelTitle)
let markLabelDescription = document.createElement('div')
markLabelDescription.classList.add('nst-label-description')
markLabelDescription.textContent = 'Оценка.'
markLabelCell.append(markLabelDescription)
let markInputCell = document.createElement('td')
markInputCell.classList.add('nst-flex')
markRow.append(markInputCell)
let markInput = document.createElement('input')
markInput.readOnly = true
markInput.value = mark
markInputCell.append(markInput)
let markSelectorsDiv = document.createElement('div')
markSelectorsDiv.classList.add('nst-mark-selectors')
markInputCell.append(markSelectorsDiv)
let marks = ['5', '4', '3', '2', '•']
marks.forEach(mark => {
let markButton = document.createElement('button')
markButton.innerText = mark
markButton.addEventListener('click', () => markInput.value = mark)
markSelectorsDiv.append(markButton)
})
let weightRow = document.createElement('tr')
templateTable.append(weightRow)
let weightLabelCell = document.createElement('td')
weightRow.append(weightLabelCell)
let weightLabelTitle = document.createElement('div')
weightLabelTitle.classList.add('nst-label-title')
weightLabelTitle.textContent = 'Вес'
weightLabelCell.append(weightLabelTitle)
let weightLabelDescription = document.createElement('div')
weightLabelDescription.classList.add('nst-label-description')
weightLabelDescription.textContent = 'Вес оценки.'
weightLabelCell.append(weightLabelDescription)
let weightInputCell = document.createElement('td')
weightInputCell.classList.add('nst-flex')
weightRow.append(weightInputCell)
let weightInput = document.createElement('input')
weightInput.type = 'number'
weightInput.value = weight
weightInputCell.append(weightInput)
return [templateTable, markInput, weightInput]
}
function highlightMark(mark) {
mark.classList.remove('nst-mark-highlight');
mark.offsetWidth;
mark.classList.add('nst-mark-highlight');
}
function cookMark(mark) {
let markValue = mark.querySelector('.nst-mark-value')
let weightValue = mark.querySelector('.nst-weight-value')
mark.addEventListener('click', () => {
let modalDiv = document.createElement('div')
let [templateTable, markInput, weightInput] = markModalTemplate(markValue.innerText, weightValue.innerText)
modalDiv.append(templateTable)
let controlsDiv = document.createElement('div')
controlsDiv.classList.add('nst-controls')
modalDiv.append(controlsDiv)
let cloneMarkButton = document.createElement('button')
cloneMarkButton.innerText = 'Клонировать'
controlsDiv.append(cloneMarkButton)
cloneMarkButton.addEventListener('click', () => {
modalDiv.closeDialog(true)
let newMark = mark.cloneNode(true)
mark.after(newMark)
cookMark(newMark)
highlightMark(newMark)
})
let deleteMarkButton = document.createElement('button')
deleteMarkButton.innerText = 'Удалить'
controlsDiv.append(deleteMarkButton)
deleteMarkButton.addEventListener('click', () => {
mark.remove()
modalDiv.closeDialog(false)
})
if (mark.dataset.assignment) {
let assignment = JSON.parse(mark.dataset.assignment)
let restoreMarkButton = document.createElement('button')
restoreMarkButton.innerText = 'Восстановить'
controlsDiv.append(restoreMarkButton)
restoreMarkButton.addEventListener('click', () => {
markInput.value = assignment.mark
weightInput.value = assignment.weight
})
let assignmentMarkButton = document.createElement('button')
assignmentMarkButton.innerText = 'Подробности'
controlsDiv.append(assignmentMarkButton)
assignmentMarkButton.addEventListener('click', () => {
// TODO Доделать
let assignmentTable = document.createElement('table')
let translations = {
'id': 'ID задания',
'assignmentName': 'Тема задания',
'activityName': 'Имя деятельности',
'problemName': 'Название задачи',
'studentId': 'ID ученика',
'subjectGroup.id': 'ID предмета',
'subjectGroup.name': 'Название предмета',
'teachers.0.id': 'ID учителя',
'teachers.0.name': 'Имя учителя',
'productId': 'ID продукта',
'isDeleted': 'Удалено',
'weight': 'Вес',
'date': 'Дата',
'description': 'Описание',
'mark': 'Оценка',
'typeId': 'ID типа задания',
'type': 'Тип задания'
}
for (let key in assignment) {
let translation = translations[key] || key
let value = assignment[key]
value = value === true ? "Да" : value === false ? "Нет" : value
let assignmentRow = document.createElement('tr')
assignmentTable.append(assignmentRow)
let assignmentLabelCell = document.createElement('td')
assignmentLabelCell.innerText = translation
assignmentRow.append(assignmentLabelCell)
let assignmentInputCell = document.createElement('td')
assignmentInputCell.classList.add('nst-flex')
assignmentRow.append(assignmentInputCell)
let assignmentInput
console.log(key, value, typeof value)
if (key === 'date') {
assignmentInput = document.createElement('input')
assignmentInput.readOnly = true
assignmentInput.type = 'date'
assignmentInput.value = value
} else if (typeof value === 'number') {
assignmentInput = document.createElement('input')
assignmentInput.readOnly = true
assignmentInput.type = 'number'
assignmentInput.value = value
} else {
assignmentInput = document.createElement('div')
assignmentInput.innerText = value
assignmentInput.classList.add('nst-area')
}
assignmentInputCell.append(assignmentInput)
}
nstModal('Подробности задания', assignmentTable, false)
})
}
nstModal('Редактирование оценки', modalDiv).then(save => {
if (save) {
markValue.innerText = markInput.value
weightValue.innerText = weightInput.value
highlightMark(mark)
}
})
})
}
function updateButtons() {
if (marksTable.querySelectorAll('.nst-row-selected').length > 0) {
addMarkButton.disabled = false
cloneRowButton.disabled = false
removeRowButton.disabled = false
} else {
addMarkButton.disabled = true
cloneRowButton.disabled = true
removeRowButton.disabled = true
}
}
updateButtons()
function cookRow(row) {
let marksCell = row.querySelector('.nst-marks-cell')
let totalCell = row.querySelector('.nst-total-cell')
// Изменение цвета оценки / балла
function calculateTotalScore() {
let markSum = 0
let weightSum = 0
Array.from(marksCell.children).forEach(markDiv => {
let markValue = markDiv.querySelector('.nst-mark-value')
let weightValue = markDiv.querySelector('.nst-weight-value')
let mark = markValue.innerText.replaceAll('•', '2')
let weight = Number(weightValue.innerText)
markValue.classList.remove(...['nst-mark-excellent', 'nst-mark-good', 'nst-mark-average', 'nst-mark-bad'])
markValue.classList.add(getMarkClass(mark))
markSum += mark * weight
weightSum += weight
})
totalCell.innerText = weightSum ? Number((markSum / weightSum).toFixed(2)) : 0
totalCell.classList.remove(...['nst-mark-excellent', 'nst-mark-good', 'nst-mark-average', 'nst-mark-bad'])
totalCell.classList.add(getMarkClass(totalCell.innerText))
}
function getMarkClass(mark) {
return Number(mark) >= 4.60 ? 'nst-mark-excellent' : Number(mark) >= 3.60 ? 'nst-mark-good' : Number(mark) >= 2.60 ? 'nst-mark-average' : 'nst-mark-bad'
}
let observer = new MutationObserver(() => {
calculateTotalScore()
})
observer.observe(marksCell, { childList: true, subtree: true })
calculateTotalScore()
// Выделение строки при нажатии на балл
totalCell.addEventListener('click', () => {
row.classList.toggle('nst-row-selected')
updateButtons()
})
}
function flattenJson(json) {
let result = {}
function flatten(obj, prefix = '') {
for (let key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
flatten(obj[key], prefix + key + '.')
} else {
result[prefix + key] = obj[key]
}
}
}
flatten(json)
return result
}
fetch('/webapi/grade/assignment/types').then((response) => {
return response.json()
}).then((types) => {
for (let day of data.weekDays) {
for (let lesson of day.lessons) {
if (Array.isArray(lesson.assignments)) {
for (let assignment of lesson.assignments) {
if (assignment.mark) {
fetch(`/webapi/student/diary/assigns/${assignment.id}`, { 'headers': { 'at': at } }).then((response) => {
return response.json()
}).then((fullAssignment) => {
// Модификация данных в удобный формат
fullAssignment.mark = assignment.mark.mark
fullAssignment.studentId = assignment.mark.studentId
fullAssignment.typeId = assignment.typeId
fullAssignment.date = fullAssignment.date.substring(0, 10)
let item = types.find(data => data.id == fullAssignment.typeId)
fullAssignment.type = item.name
fullAssignment = flattenJson(fullAssignment)
for (let key in fullAssignment) {
if (fullAssignment[key] == null) delete fullAssignment[key]
}
// Объявление / создание ряда
let row = Array.from(marksTable.rows).find(r => r.querySelector('.nst-name-input').value == lesson.subjectName)
if (!row) {
row = createRow(lesson.subjectName)
marksTable.append(row)
cookRow(row)
}
// Добавление оценки
let createdMark = createMark(fullAssignment.mark, fullAssignment.weight, fullAssignment)
row.querySelector('.nst-marks-cell').append(createdMark)
cookMark(createdMark)
})
}
}
}
}
}
}).catch(err => {
console.error('Произошла ошибка: ', err)
})
})
})
nstModal('Предпросмотр оценок', contentDiv, false)
})
previewMarksWrapper.append(previewMarksButton)
})
waitForElement('.top-right-menu').then((elm) => {
elm.prepend(settings)
})
GM_addStyle(`
@import url('https://fonts.googleapis.com/css2?family=Nunito&display=swap');
:root {
--nst-primary: #64a0c8;
--nst-secondary: #415f78;
--nst-tertiary: #374b5f;
--nst-quaternary: #232a32;
--nst-quinary: #1a1c1e;
--nst-senary: #141618;
--nst-text-primary: #c8e6ff;
--nst-text-secondary: #aadcff;
--nst-text-tertiary: #87afc8;
}
.nst-no-scroll {
touch-action: none;
overflow: hidden;
}
.nst-flex {
display: flex;
flex-direction: column;
}
.nst-flex input {
flex: 1;
}
.nst-settings-icon {
display: flex !important;
justify-content: center;
color: white;
scale: 0.75;
}
.nst-dialog {
border: none;
outline: none;
background: var(--nst-quinary);
border-radius: 32px;
box-shadow: rgba(0, 0, 0, 0.25) 0 0 25px;
padding: 0;
}
.nst-dialog-wrapper {
display: flex;
flex-direction: column;
padding: 24px;
max-height: calc(100vh - 48px);
max-width: calc(100vw - 48px);
}
.nst-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: auto;
}
.nst-dialog .preview-marks-wrapper {
display: flex;
flex-wrap: wrap;
justify-content: center;
padding-top: 16px;
gap: 8px;
}
.nst-dialog * {
font-family: 'Nunito';
color: var(--nst-text-secondary);
}
.nst-dialog .nst-headline:first-child {
margin-top: 0px;
}
.nst-dialog .nst-headline {
color: var(--nst-text-primary);
font-size: 1.5em;
margin: 0px;
margin-bottom: 16px;
}
.nst-dialog .nst-actions {
margin-top: 16px;
display: flex;
gap: 8px;
justify-content: flex-end;
}
.nst-dialog button {
cursor: pointer;
color: var(--button-color);
border-radius: 28px;
padding: 12px;
margin: 0;
border: none;
outline: none;
transition: 0.2s;
background: var(--nst-quaternary);
}
.nst-dialog button:not([disabled]):hover {
background: var(--nst-tertiary);
}
.nst-dialog button:not([disabled]):active {
background: var(--nst-secondary);
}
.nst-dialog button[disabled] {
opacity: 0.5;
cursor: default;
}
.nst-dialog input {
text-shadow: none;
box-shadow: none;
line-height: normal;
border: 2px solid var(--nst-tertiary);
color: var(--nst-text-secondary);
border-radius: 16px;
padding: 12px;
background: var(--nst-quaternary);
transition: 0.2s;
}
.nst-dialog input::-webkit-outer-spin-button,
.nst-dialog input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.nst-dialog input[disabled] {
border: 2px solid var(--nst-tertiary);
opacity: 0.5;
}
.nst-dialog input[disabled]:hover,
.nst-dialog input[disabled]:focus,
.nst-dialog input[disabled]:active {
border: 2px solid transparent;
border-radius: 16px;
color: var(--nst-text-secondary);
box-shadow: none;
padding: 12px;
}
.nst-dialog input:hover {
border: 2px solid transparent;
color: var(--nst-text-secondary);
box-shadow: none;
}
.nst-dialog input:focus,
.nst-dialog input:active {
border: 2px solid transparent;
color: var(--nst-text-secondary);
background: var(--nst-tertiary);
box-shadow: none;
}
.nst-password {
padding-right: 48px;
}
.nst-password ~ .nst-password-eye {
transition: 0.2s;
position: absolute;
display: flex;
justify-content: center;
align-items: center;
right: 8px;
top: 8px;
width: 48px;
height: 48px;
}
.nst-password ~ .nst-password-eye:hover {
opacity: 0.75;
}
.nst-password ~ .nst-password-eye:active {
opacity: 0.5;
}
.nst-password ~ .nst-password-eye::after {
transition: 0.2s;
content: "";
position: absolute;
width: 60%;
height: 2px;
background-color: currentColor;
transform: rotate(45deg);
border-radius: 2px;
}
.nst-password[type="password"] ~ .nst-password-eye::after {
width: 0%;
}
.nst-password ~ .nst-password-eye .nst-password-eye-inner {
width: 24px;
height: 16px;
border: 2px solid currentColor;
border-radius: 50%;
position: absolute;
}
.nst-password ~ .nst-password-eye .nst-password-eye-outer {
width: 8px;
height: 8px;
border: 2px solid currentColor;
border-radius: 50%;
position: absolute;
}
.nst-dialog .nst-area {
border-radius: 16px;
background: var(--nst-quaternary);
padding: 16px;
width: 100%;
box-sizing: border-box;
}
.nst-dialog .nst-switch {
position: relative;
display: inline-block;
width: 3.5em;
height: 2em;
margin: 0;
}
.nst-dialog .nst-switch .nst-hide {
opacity: 0;
width: 0;
height: 0;
}
.nst-dialog .nst-switch div {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--nst-quaternary);
border: 2px solid var(--nst-tertiary);
border-radius: 24px;
transition: .4s;
}
.nst-dialog .nst-switch div:before {
position: absolute;
content: "";
height: 1.2em;
width: 1.2em;
left: calc(0.4em - 2px);
top: calc(0.4em - 2px);
background: var(--nst-tertiary);
border-radius: 50%;
transition: .4s;
}
.nst-dialog .nst-switch input:checked + div {
background: var(--nst-primary);
border: 2px solid transparent;
}
.nst-dialog .nst-switch input:checked + div:before {
transform: translateX(1.4em);
background: rgba(0, 0, 0, 0.5);
}
.nst-dialog table {
margin-left: auto;
margin-right: auto;
}
.nst-dialog td {
padding: 8px;
position: relative;
}
.nst-total-cell {
right: 0;
position: sticky !important;
cursor: pointer;
background: var(--nst-quinary);
transition: 0.1s;
}
tr.nst-row-selected .nst-total-cell:last-child {
background: var(--nst-senary);
}
.nst-dialog .nst-marks-table-wrapper tr {
transition: 0.2s;
}
.nst-dialog .nst-marks-table-wrapper tr.nst-row-selected {
background: var(--nst-senary);
}
.nst-dialog .nst-marks-table-wrapper {
overflow-y: auto;
border-radius: 24px;
}
.nst-marks-cell {
display: flex;
}
.nst-controls {
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
gap: 8px;
padding-top: 8px;
}
.nst-controls button {
flex-grow: 1;
}
.nst-dialog[open] {
animation: nst-show-dialog 0.5s forwards;
}
.nst-dialog.nst-hide-dialog {
animation: nst-hide-dialog 0.5s forwards;
}
@keyframes nst-show-dialog {
from {
opacity: 0;
transform: scale(0.5);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes nst-hide-dialog {
to {
opacity: 0;
transform: scale(0.5);
}
}
.nst-dialog::backdrop {
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(5px);
animation: none;
}
.nst-dialog[open]::backdrop {
animation: nst-show-opacity 0.5s forwards;
}
.nst-dialog.nst-hide-dialog::backdrop {
animation: nst-hide-opacity 0.5s forwards;
}
@keyframes nst-show-opacity {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes nst-hide-opacity {
to {
opacity: 0;
}
}
.nst-label-title {
font-size: 18px;
}
.nst-label-description {
font-size: 12px;
color: var(--nst-text-tertiary);
}
.nst-mark {
min-width: 24px;
display: flex;
flex-flow: column wrap;
align-items: stretch;
cursor: pointer;
animation: nst-show-opacity 0.5s forwards;
}
.nst-mark-selectors {
display: flex;
gap: 8px;
padding-top: 8px;
justify-content: space-between;
}
.nst-mark p {
text-align: center;
margin: 0px;
}
.nst-mark p:first-child {
font-size: large;
}
.nst-mark p:nth-child(2) {
color: gray;
font-size: x-small;
}
.nst-mark-highlight {
animation: nst-mark-highlight 3s forwards;
}
@keyframes nst-mark-highlight {
0% {
opacity: 1;
} 16% {
opacity: 0;
} 32% {
opacity: 1;
} 48% {
opacity: 0;
} 64% {
opacity: 1;
} 80% {
opacity: 0;
} 96% {
opacity: 1;
}
}
.nst-mark-excellent {
color: #96e400;
}
.nst-mark-good {
color: #00c8ff;
}
.nst-mark-average {
color: #f09600;
}
.nst-mark-bad {
color: #ff3232;
}
`)