Implemented notes importing

This commit is contained in:
2022-09-07 00:01:54 +03:00
parent c527fcac39
commit cde0442489
3 changed files with 1394 additions and 97 deletions

View File

@@ -1,6 +1,16 @@
(function runTwitchNotes() {
function save(filename, data) {
const blob = new Blob([data], {type: 'text/json'});
const elem = window.document.createElement('a');
elem.href = window.URL.createObjectURL(blob);
elem.download = filename;
document.body.appendChild(elem);
elem.click();
document.body.removeChild(elem);
}
const Notes = {
inputData: {},
getSavedUserList: () => {
return JSON.parse(localStorage.getItem(LS_UserList) || "[]");
},
@@ -13,11 +23,225 @@
}
},
saveNote: (username, note) => {
console.log({username, note});
Notes.addUserToSavedList(username);
localStorage.setItem(LS_Prefix + username, note);
},
getNote: (username) => {
return localStorage.getItem(LS_Prefix + username) || "";
},
exportNotes: () => {
save('twitch-notes-' + (Date.now()) + '.json', JSON.stringify({
users: Notes.getSavedUserList(),
notes: Notes.getSavedUserList().map(user => {
return {user: user, note: Notes.getNote(user)};
}),
settings: {},
}));
},
importNotesConflictResolve: (user, resultsContainer, container, blurredBackground) => {
const diffBlurredBackground = createElement('div', null, 'twitch-notes-blurred-background');
const diffContainer = createElement('div', '<strong>Select which note to keep for <em>' + user + '</em></strong><br /><em>You can modify notes to merge them and keep updated one</em><br /><br />', 'twitch-notes-center-floating-container');
const inputContainer = createElement('div', null, null, {
display: 'flex',
flexDirection: 'row',
flexWrap: 'no-wrap',
width: '100%',
});
const valueStyle = {
minWidth: '320px',
padding: '6px',
border: '1px solid #FFFFFF19'
};
const valueAttr = {contentEditable: 'true'};
const localValueContainer = createElement('div');
const pLocal = createElement('p', '<strong>Locally saved note</strong>');
localValueContainer.appendChild(pLocal);
const localValue = createElement('div', Notes.getNote(user), null, valueStyle, valueAttr, 'local-value-container');
const localValueSave = createElement('button', 'Save this', 'twitch-notes-settings-button');
localValueSave.addEventListener('click', () => {
const val = document.getElementById('local-value-container').innerHTML;
Notes.inputData.notes = Notes.inputData.notes.map(n => n.user === user ? {
user: n.user,
note: val,
resolved: true
} : n);
diffBlurredBackground.remove();
resultsContainer.remove();
Notes.importNotesResult(container, blurredBackground);
});
localValueContainer.appendChild(localValue);
localValueContainer.innerHTML += '<br />';
localValueContainer.appendChild(localValueSave);
const importedValueContainer = createElement('div');
const iLocal = createElement('p', '<strong>Imported note</strong>');
importedValueContainer.appendChild(iLocal);
const importedValue = createElement('div', Notes.inputData.notes.filter(n => n.user === user)[0].note || '', null, valueStyle, valueAttr, 'import-value-container');
const importedValueSave = createElement('button', 'Save this', 'twitch-notes-settings-button');
importedValueSave.addEventListener('click', () => {
const val = document.getElementById('import-value-container').innerHTML;
Notes.inputData.notes = Notes.inputData.notes.map(n => n.user === user ? {
user: n.user,
note: val,
resolved: true
} : n);
diffBlurredBackground.remove();
resultsContainer.remove();
Notes.importNotesResult(container, blurredBackground);
});
importedValueContainer.appendChild(importedValue);
importedValueContainer.innerHTML += '<br />';
importedValueContainer.appendChild(importedValueSave);
inputContainer.appendChild(localValueContainer);
inputContainer.appendChild(importedValueContainer);
diffContainer.appendChild(inputContainer);
diffBlurredBackground.appendChild(diffContainer);
document.body.appendChild(diffBlurredBackground);
},
importNotesResult: (container, blurredBackground) => {
const willBeAdded = [];
let willBeOverwritten = [];
const noChanges = [];
for (let user of Notes.inputData.users) {
if (Notes.getNote(user)) {
if (Notes.inputData.notes.find(note => note.user === user).resolved || Notes.getNote(user) !== Notes.inputData.notes.find(note => note.user === user).note) {
willBeOverwritten.push(user);
} else {
noChanges.push(user);
}
} else {
willBeAdded.push(user);
}
}
const resultsContainer = createElement('div', null);
if (willBeOverwritten.length) {
const overwrittenContainer = createElement('div', '<strong>Note conflicts found for:</strong><br />', null, {
marginTop: '1rem',
});
for (let item of willBeOverwritten) {
const row = createElement('div', item + ' ', null, {
color: Notes.inputData.notes.find(note => note.user === item).resolved ? 'green' : 'red',
});
const resolve = createElement('a', Notes.inputData.notes.find(note => note.user === item).resolved ? '[update]' : '[resolve]', null, {
cursor: 'pointer',
});
resolve.addEventListener('click', () => {
Notes.importNotesConflictResolve(item, resultsContainer, container, blurredBackground);
})
row.appendChild(resolve);
overwrittenContainer.appendChild(row);
}
resultsContainer.appendChild(overwrittenContainer);
}
if (willBeAdded.length) {
const addedContainer = createElement('div', '<strong>Note will be added for:</strong><br />' + willBeAdded.join('<br />'), null, {
marginTop: '1rem',
color: 'white'
})
resultsContainer.appendChild(addedContainer);
}
if (noChanges.length) {
const noChangesContainer = createElement('div', '<strong>No changes for:</strong><br />' + noChanges.join('<br />'), null, {
marginTop: '1rem',
color: 'gray'
})
resultsContainer.appendChild(noChangesContainer);
}
const spacer = createElement('div', null, null, {height: '24px'});
resultsContainer.appendChild(spacer);
/*
TODO: make popup to allow checking differences between existing notes and ones that would override them
TODO: allow to edit notes and select which one to keep after import, if any content was modified after opening comparison window show that record was merged as a status
TODO: show visually that records have some data that will be merged or overwritten entirely
*/
willBeOverwritten = willBeOverwritten.filter(x => !Notes.inputData.notes.find(n => n.user === x).resolved);
const saveButton = createElement('button', 'Complete import', 'twitch-notes-settings-button',
willBeOverwritten.length > 0 ? {background: 'gray'} : null,
willBeOverwritten.length > 0 ? {disabled: 'true'} : null)
resultsContainer.appendChild(saveButton)
if (willBeOverwritten.length > 0) {
const note = createElement('div', '<em>Import cannot be completed while there are unresolved conflicts</em>', null, {
color: 'gray',
});
resultsContainer.appendChild(note);
} else {
saveButton.addEventListener('click', () => {
for (let note of Notes.inputData.notes) {
Notes.saveNote(note.user, note.note);
}
blurredBackground.remove();
Notes.inputData = {};
});
}
container.appendChild(resultsContainer);
},
importNotes: () => {
const blurredBackground = createElement('div', null, 'twitch-notes-blurred-background');
const container = createElement('div', '<strong>WARNING!</strong> <em>This action might override existing data!</em><br /><br />', 'twitch-notes-center-floating-container');
const inputBlock = createElement('div', 'Select exported twitch notes file<br /><br />');
const input = createElement('input', null, 'twitch-notes-file-input', null, {
type: 'file',
accept: '.json'
});
const settingsCloseButton = createElement('button', xButton, 'twitch-notes-settings-close-button', {
position: 'absolute',
top: '1rem',
right: '1rem',
});
inputBlock.appendChild(input);
settingsCloseButton.addEventListener('click', () => {
blurredBackground.remove();
});
input.addEventListener('change', () => {
let file = input.files[0];
if (!file) {
return;
}
let reader = new FileReader();
reader.readAsText(file, "UTF-8");
reader.onload = function (evt) {
Notes.inputData = JSON.parse(evt.target.result.toString());
if (Notes.inputData) {
inputBlock.style.display = 'none';
Notes.importNotesResult(container, blurredBackground);
}
}
reader.onerror = function () {
console.error("error reading file");
}
});
container.appendChild(settingsCloseButton);
container.appendChild(inputBlock);
blurredBackground.appendChild(container);
document.body.appendChild(blurredBackground);
},
clearAllData: () => {
console.log('TODO: Implement Notes Data Clearing');
},
openAllNotes: () => {
console.log('TODO: Implement Open All Notes functionality');
}
}
@@ -47,7 +271,7 @@
}
}
function createElement(tag, body, classes, styles, attributes) {
function createElement(tag, body, classes, styles, attributes, id) {
const element = document.createElement(tag);
if (body) {
@@ -80,6 +304,8 @@
}
}
element.setAttribute('id', id ? id : 'element-' + Math.floor((Math.random() * 10000000)))
return element;
}
@@ -125,7 +351,7 @@
isMouseDown = true;
});
const closeButton = createElement('span', "X", 'twitch-note-close-button')
const closeButton = createElement('span', xButton, 'twitch-note-close-button')
closeButton.addEventListener("click", function close() {
removeContainer(username);
});
@@ -173,7 +399,7 @@
});
const img = createElement('img', null, null, {
height: '18px',
height: '18px',
paddingRight: '4px'
}, {
src: "https://cdn.rdarius.lt/icons/32-id-card.png"
@@ -194,14 +420,95 @@
}
}
function createChatSettingsLine(text) {
const element = createElement('div', null, 'twitch-notes-settings-option-line');
const button = createElement('button', null, 'twitch-notes-settings-option-line-button');
const buttonContainer = createElement('div', null, 'twitch-notes-settings-option-line-button-container');
const buttonContainerContent = createElement('div', text, 'twitch-notes-settings-option-line-button-container-content');
buttonContainer.appendChild(buttonContainerContent);
button.appendChild(buttonContainer);
element.appendChild(button);
return element;
}
function createChatSettingsSeparator() {
return createElement('div', null, 'twitch-note-settings-separator');
}
function toggleSettings() {
if (!settingsWindow) {
const settingsContainer = createElement('div', null, 'twitch-notes-settings-container');
const settingsBalloon = createElement('div', null, 'twitch-notes-settings-balloon');
const settingsPopover = createElement('div', null, 'twitch-notes-settings-popover');
// startOf: settings header
const settingsHeader = createElement('div', null, 'twitch-notes-settings-header');
const settingsHeaderLeftElement = createElement('div', null, 'twitch-notes-settings-header-left-element')
settingsHeader.appendChild(settingsHeaderLeftElement);
const settingsHeaderCenterElement = createElement('div', null, 'twitch-notes-settings-header-center-element')
settingsHeader.appendChild(settingsHeaderCenterElement);
const settingsHeaderCenterElementContent = createElement('p', 'Twitch Notes Settings', 'twitch-notes-settings-header-center-element-content')
settingsHeaderCenterElement.appendChild(settingsHeaderCenterElementContent);
const settingsHeaderRightElement = createElement('div', null, 'twitch-notes-settings-header-right-element')
settingsHeader.appendChild(settingsHeaderRightElement);
const settingsCloseButton = createElement('button', xButton, 'twitch-notes-settings-close-button');
settingsHeaderRightElement.appendChild(settingsCloseButton);
settingsPopover.appendChild(settingsHeader);
// endOf: settings header
settingsCloseButton.addEventListener('click', toggleSettings);
// startOf: scrollable area
const settingsScrollableArea = createElement('div', null, 'twitch-notes-settings-scrollable-area');
const settingsContent = createElement('div', null, 'twitch-notes-settings-content');
settingsScrollableArea.appendChild(settingsContent);
const exportNotes = createChatSettingsLine('Export Notes');
settingsContent.appendChild(exportNotes);
const importNotes = createChatSettingsLine('Import Notes');
settingsContent.appendChild(importNotes);
const clearData = createChatSettingsLine('Clear Data');
settingsContent.appendChild(clearData);
settingsContent.appendChild(createChatSettingsSeparator());
const openAllNotes = createChatSettingsLine('View All Notes');
settingsContent.appendChild(openAllNotes);
exportNotes.addEventListener('click', Notes.exportNotes);
importNotes.addEventListener('click', Notes.importNotes);
clearData.addEventListener('click', Notes.clearAllData);
openAllNotes.addEventListener('click', Notes.openAllNotes);
settingsPopover.appendChild(settingsScrollableArea);
// endOf: scrollable area
settingsBalloon.appendChild(settingsPopover);
settingsContainer.appendChild(settingsBalloon);
settingsWindow = settingsContainer;
document.body.appendChild(settingsWindow);
settingsWindowOpen = true;
return;
}
if (settingsWindowOpen) {
settingsWindow.style.display = 'none';
settingsWindowOpen = false;
} else {
settingsWindow.style.display = 'block';
settingsWindowOpen = true;
}
console.log('toggle settings');
}
const xButton = `<svg width="100%" height="100%" viewBox="0 0 20 20" x="0px" y="0px" class="twitch-notes-x-button"><g><path d="M8.5 10L4 5.5 5.5 4 10 8.5 14.5 4 16 5.5 11.5 10l4.5 4.5-1.5 1.5-4.5-4.5L5.5 16 4 14.5 8.5 10z"></path></g></svg>`;
let openContainers = {};
let isMouseDown = false;
let activeContainer = null;
// let settingsWindowOpen = false;
let settingsWindowOpen = false;
let settingsWindow = null;
let LS_UserList = "twitch-note-all-users-list";
let LS_Prefix = "twitch-note-";