mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2026-08-30 08:39:12 +01:00
[MIRROR] allow chat setting ex / import (#10315)
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
co-authored by
Kashargul
parent
751179cdbe
commit
96ffd04c6a
@@ -11,6 +11,7 @@ import { createPage } from './model';
|
||||
export const getChatData = createAction('chat/getChatData');
|
||||
export const loadChat = createAction('chat/load');
|
||||
export const rebuildChat = createAction('chat/rebuild');
|
||||
export const clearChat = createAction('chat/clear');
|
||||
export const updateMessageCount = createAction('chat/updateMessageCount');
|
||||
export const addChatPage = createAction('chat/addPage', () => ({
|
||||
payload: createPage(),
|
||||
|
||||
@@ -135,14 +135,10 @@ async function getRound(
|
||||
'</body>\n' +
|
||||
'</html>\n';
|
||||
|
||||
try {
|
||||
fileHandle.createWritable().then((writableHandle) => {
|
||||
writableHandle.write(pageHtml);
|
||||
writableHandle.close();
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
fileHandle.createWritable().then((writableHandle) => {
|
||||
writableHandle.write(pageHtml);
|
||||
writableHandle.close();
|
||||
});
|
||||
|
||||
resolve();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import DOMPurify from 'dompurify';
|
||||
import { selectGame } from '../game/selectors';
|
||||
import {
|
||||
addHighlightSetting,
|
||||
importSettings,
|
||||
loadSettings,
|
||||
removeHighlightSetting,
|
||||
updateHighlightSetting,
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
addChatPage,
|
||||
changeChatPage,
|
||||
changeScrollTracking,
|
||||
clearChat,
|
||||
getChatData,
|
||||
loadChat,
|
||||
moveChatPageLeft,
|
||||
@@ -254,7 +256,7 @@ export const chatMiddleware = (store) => {
|
||||
game.databaseBackendEnabled,
|
||||
);
|
||||
// Load the chat once settings are loaded
|
||||
if (!initialized && (settings.initialized || settings.firstLoad)) {
|
||||
if (!initialized && settings.initialized) {
|
||||
initialized = true;
|
||||
setInterval(() => {
|
||||
saveChatToStorage(store);
|
||||
@@ -338,12 +340,14 @@ export const chatMiddleware = (store) => {
|
||||
type === loadSettings.type ||
|
||||
type === addHighlightSetting.type ||
|
||||
type === removeHighlightSetting.type ||
|
||||
type === updateHighlightSetting.type
|
||||
type === updateHighlightSetting.type ||
|
||||
type === importSettings.type
|
||||
) {
|
||||
next(action);
|
||||
const nextSettings = selectSettings(store.getState());
|
||||
chatRenderer.setHighlight(
|
||||
settings.highlightSettings,
|
||||
settings.highlightSettingById,
|
||||
nextSettings.highlightSettings,
|
||||
nextSettings.highlightSettingById,
|
||||
);
|
||||
|
||||
return;
|
||||
@@ -367,6 +371,10 @@ export const chatMiddleware = (store) => {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (type === clearChat.type) {
|
||||
chatRenderer.clearChat();
|
||||
return;
|
||||
}
|
||||
if (type === purgeChatMessageArchive.type) {
|
||||
chatRenderer.purgeMessageArchive();
|
||||
storedRounds = [];
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { importSettings } from '../settings/actions';
|
||||
import {
|
||||
addChatPage,
|
||||
changeChatPage,
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
updateMessageCount,
|
||||
} from './actions';
|
||||
import { canPageAcceptType, createMainPage } from './model';
|
||||
import type { Page } from './types';
|
||||
|
||||
const mainPage = createMainPage();
|
||||
|
||||
@@ -127,6 +129,24 @@ export const chatReducer = (state = initialState, action) => {
|
||||
},
|
||||
};
|
||||
}
|
||||
if (type === importSettings.type) {
|
||||
const pagesById: Record<string, Page>[] = payload.newPages;
|
||||
if (!pagesById) {
|
||||
return state;
|
||||
}
|
||||
const newPageIds: string[] = Object.keys(pagesById);
|
||||
if (!newPageIds) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const nextState = {
|
||||
...state,
|
||||
currentPageId: newPageIds[0],
|
||||
pages: [...newPageIds],
|
||||
pageById: { ...pagesById },
|
||||
};
|
||||
return nextState;
|
||||
}
|
||||
if (type === changeChatPage.type) {
|
||||
const { pageId } = payload;
|
||||
const page = {
|
||||
|
||||
@@ -292,7 +292,7 @@ class ChatRenderer {
|
||||
}
|
||||
|
||||
assignStyle(style = {}) {
|
||||
for (let key of Object.keys(style)) {
|
||||
for (const key of Object.keys(style)) {
|
||||
if (this.rootNode) {
|
||||
this.rootNode.style.setProperty(key, style[key]);
|
||||
}
|
||||
@@ -313,7 +313,7 @@ class ChatRenderer {
|
||||
const highlightWholeMessage = setting.highlightWholeMessage;
|
||||
const matchWord = setting.matchWord;
|
||||
const matchCase = setting.matchCase;
|
||||
const allowedRegex = /^[a-z0-9_\-$/^[\s\]\\]+$/gi;
|
||||
const allowedRegex = /^[a-zа-яё0-9_\-$/^[\s\]\\]+$/gi;
|
||||
const regexEscapeCharacters = /[!#$%^&*)(+=.<>{}[\]:;'"|~`_\-\\/]/g;
|
||||
const lines = String(text)
|
||||
.split(',')
|
||||
@@ -822,6 +822,31 @@ class ChatRenderer {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @clearChat
|
||||
* @copyright 2023
|
||||
* @author Cheffie
|
||||
* @link https://github.com/CheffieGithub
|
||||
* @license MIT
|
||||
*/
|
||||
clearChat() {
|
||||
const messages = this.visibleMessages;
|
||||
this.visibleMessages = [];
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const message = messages[i];
|
||||
if (this.rootNode && message.node instanceof HTMLElement) {
|
||||
this.rootNode.removeChild(message.node);
|
||||
}
|
||||
// Mark this message as pruned
|
||||
message.node = 'pruned';
|
||||
}
|
||||
// Remove pruned messages from the message array
|
||||
this.messages = this.messages.filter(
|
||||
(message) => message.node !== 'pruned',
|
||||
);
|
||||
logger.log(`Cleared chat`);
|
||||
}
|
||||
|
||||
saveToDisk(
|
||||
logLineCount: number = 0,
|
||||
startLine: number = 0,
|
||||
|
||||
@@ -64,7 +64,7 @@ const regexParseNode = (params: {
|
||||
fragment.appendChild(new_node);
|
||||
}
|
||||
// Commit the fragment
|
||||
if (node && node.parentNode) {
|
||||
if (node?.parentNode) {
|
||||
node.parentNode.replaceChild(fragment, node);
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@ export const replaceInTextNode =
|
||||
for (let word of words) {
|
||||
// Capture if the word is at the beginning, end, middle,
|
||||
// or by itself in a message
|
||||
wordRegexStr += `^${word}\\W|\\W${word}\\W|\\W${word}$|^${word}$`;
|
||||
wordRegexStr += `^${word}\\s\\W|\\s\\W${word}\\s\\W|\\s\\W${word}$|^${word}\\s\\W$`;
|
||||
// Make sure the last character for the expression is NOT '|'
|
||||
if (++i !== words.length) {
|
||||
wordRegexStr += '|';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export type message = {
|
||||
node?: Element | string;
|
||||
node?: HTMLElement | string;
|
||||
type: string;
|
||||
text?: string;
|
||||
html?: string;
|
||||
|
||||
@@ -13,11 +13,16 @@ import {
|
||||
} from 'tgui-core/components';
|
||||
import { toFixed } from 'tgui-core/math';
|
||||
|
||||
import { purgeChatMessageArchive, saveChatToDisk } from '../../chat/actions';
|
||||
import {
|
||||
clearChat,
|
||||
purgeChatMessageArchive,
|
||||
saveChatToDisk,
|
||||
} from '../../chat/actions';
|
||||
import { MESSAGE_TYPES } from '../../chat/constants';
|
||||
import { useGame } from '../../game';
|
||||
import { updateSettings, updateToggle } from '../actions';
|
||||
import { exportSettings, updateSettings, updateToggle } from '../actions';
|
||||
import { selectSettings } from '../selectors';
|
||||
import { importChatSettings } from '../settingsImExport';
|
||||
|
||||
export const ExportTab = (props) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -42,6 +47,7 @@ export const ExportTab = (props) => {
|
||||
{!game.databaseBackendEnabled &&
|
||||
(logEnable ? (
|
||||
<Button.Confirm
|
||||
tooltip="Disable local chat logging"
|
||||
icon="ban"
|
||||
color="red"
|
||||
confirmIcon="ban"
|
||||
@@ -59,6 +65,7 @@ export const ExportTab = (props) => {
|
||||
</Button.Confirm>
|
||||
) : (
|
||||
<Button
|
||||
tooltip="Enable local chat logging"
|
||||
icon="download"
|
||||
color="green"
|
||||
onClick={() => {
|
||||
@@ -272,28 +279,67 @@ export const ExportTab = (props) => {
|
||||
)}
|
||||
</LabeledList>
|
||||
<Divider />
|
||||
<Button icon="save" onClick={() => dispatch(saveChatToDisk())}>
|
||||
Save chat log
|
||||
</Button>
|
||||
{!game.databaseBackendEnabled && (
|
||||
<Button.Confirm
|
||||
disabled={purgeButtonText === 'Purged!'}
|
||||
icon="trash"
|
||||
color="red"
|
||||
confirmIcon="trash"
|
||||
confirmColor="red"
|
||||
confirmContent="Are you sure?"
|
||||
onClick={() => {
|
||||
dispatch(purgeChatMessageArchive());
|
||||
setPurgeButtonText('Purged!');
|
||||
setTimeout(() => {
|
||||
setPurgeButtonText('Purge message archive');
|
||||
}, 1000);
|
||||
}}
|
||||
>
|
||||
{purgeButtonText}
|
||||
</Button.Confirm>
|
||||
)}
|
||||
<Stack fill>
|
||||
<Stack.Item mt={0.15}>
|
||||
<Button
|
||||
icon="compact-disc"
|
||||
tooltip="Export chat settings"
|
||||
onClick={() => dispatch(exportSettings())}
|
||||
>
|
||||
Export settings
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item mt={0.15}>
|
||||
<Button.File
|
||||
accept=".json"
|
||||
tooltip="Import chat settings"
|
||||
icon="arrow-up-from-bracket"
|
||||
onSelectFiles={(files) => importChatSettings(files)}
|
||||
>
|
||||
Import settings
|
||||
</Button.File>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow mt={0.15}>
|
||||
<Button
|
||||
icon="save"
|
||||
tooltip="Export current tab history into HTML file"
|
||||
onClick={() => dispatch(saveChatToDisk())}
|
||||
>
|
||||
Save chat log
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item mt={0.15}>
|
||||
<Button.Confirm
|
||||
icon="trash"
|
||||
tooltip="Erase current tab history"
|
||||
onClick={() => dispatch(clearChat())}
|
||||
>
|
||||
Clear chat
|
||||
</Button.Confirm>
|
||||
</Stack.Item>
|
||||
{!game.databaseBackendEnabled && (
|
||||
<Stack.Item mt={0.15}>
|
||||
<Button.Confirm
|
||||
disabled={purgeButtonText === 'Purged!'}
|
||||
icon="trash"
|
||||
tooltip="Erase current tab history"
|
||||
color="red"
|
||||
confirmIcon="trash"
|
||||
confirmColor="red"
|
||||
confirmContent="Are you sure?"
|
||||
onClick={() => {
|
||||
dispatch(purgeChatMessageArchive());
|
||||
setPurgeButtonText('Purged!');
|
||||
setTimeout(() => {
|
||||
setPurgeButtonText('Purge message archive');
|
||||
}, 1000);
|
||||
}}
|
||||
>
|
||||
{purgeButtonText}
|
||||
</Button.Confirm>
|
||||
</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -26,3 +26,10 @@ export const removeHighlightSetting = createAction(
|
||||
export const updateHighlightSetting = createAction(
|
||||
'settings/updateHighlightSetting',
|
||||
);
|
||||
export const exportSettings = createAction('settings/export');
|
||||
export const importSettings = createAction(
|
||||
'settings/import',
|
||||
(settings, pages) => ({
|
||||
payload: { newSettings: settings, newPages: pages },
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -9,6 +9,8 @@ import { storage } from 'common/storage';
|
||||
import { setClientTheme } from '../themes';
|
||||
import {
|
||||
addHighlightSetting,
|
||||
exportSettings,
|
||||
importSettings,
|
||||
loadSettings,
|
||||
removeHighlightSetting,
|
||||
updateHighlightSetting,
|
||||
@@ -17,6 +19,7 @@ import {
|
||||
} from './actions';
|
||||
import { FONTS_DISABLED } from './constants';
|
||||
import { selectSettings } from './selectors';
|
||||
import { exportChatSettings } from './settingsImExport';
|
||||
|
||||
let statFontTimer: NodeJS.Timeout;
|
||||
let statTabsTimer: NodeJS.Timeout;
|
||||
@@ -90,13 +93,20 @@ export function settingsMiddleware(store) {
|
||||
store.dispatch(loadSettings(settings));
|
||||
});
|
||||
}
|
||||
if (type === exportSettings.type) {
|
||||
const state = store.getState();
|
||||
const settings = selectSettings(state);
|
||||
exportChatSettings(settings, state.chat.pageById);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
type !== updateSettings.type &&
|
||||
type !== updateToggle.type &&
|
||||
type !== loadSettings.type &&
|
||||
type !== addHighlightSetting.type &&
|
||||
type !== removeHighlightSetting.type &&
|
||||
type !== updateHighlightSetting.type
|
||||
type !== updateHighlightSetting.type &&
|
||||
type !== importSettings.type
|
||||
) {
|
||||
return next(action);
|
||||
}
|
||||
@@ -112,6 +122,9 @@ export function settingsMiddleware(store) {
|
||||
|
||||
const settings = selectSettings(store.getState());
|
||||
|
||||
if (importSettings.type) {
|
||||
setClientTheme(settings.theme);
|
||||
}
|
||||
// Update stat panel settings
|
||||
setStatTabsStyle(settings.statTabsStyle);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { MESSAGE_TYPES } from '../chat/constants';
|
||||
import {
|
||||
addHighlightSetting,
|
||||
changeSettingsTab,
|
||||
importSettings,
|
||||
loadSettings,
|
||||
openChatSettings,
|
||||
removeHighlightSetting,
|
||||
@@ -57,7 +58,6 @@ const initialState = {
|
||||
exportEnd: 0,
|
||||
lastId: null,
|
||||
initialized: false,
|
||||
firstLoad: false,
|
||||
storedTypes: {},
|
||||
hideImportantInAdminTab: false,
|
||||
interleave: false,
|
||||
@@ -97,7 +97,7 @@ export function settingsReducer(
|
||||
...state,
|
||||
...payload,
|
||||
};
|
||||
nextState.firstLoad = true;
|
||||
nextState.initialized = true;
|
||||
return nextState;
|
||||
}
|
||||
|
||||
@@ -146,6 +146,18 @@ export function settingsReducer(
|
||||
}
|
||||
}
|
||||
|
||||
case importSettings.type: {
|
||||
const newSettings = payload.newSettings;
|
||||
if (!newSettings) {
|
||||
return state;
|
||||
}
|
||||
const nextState = {
|
||||
...state,
|
||||
...newSettings,
|
||||
};
|
||||
return nextState;
|
||||
}
|
||||
|
||||
case toggleSettings.type: {
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useDispatch } from 'tgui/backend';
|
||||
|
||||
import type { Page } from '../chat/types';
|
||||
import { importSettings } from './actions';
|
||||
|
||||
export function exportChatSettings(
|
||||
settings: Record<string, any>,
|
||||
pages: Record<string, Page>[],
|
||||
) {
|
||||
const opts: SaveFilePickerOptions = {
|
||||
id: `ss13-chatprefs-${Date.now()}`,
|
||||
suggestedName: `ss13-chatsettings-${new Date().toJSON().slice(0, 10)}.json`,
|
||||
types: [
|
||||
{
|
||||
description: 'SS13 file',
|
||||
accept: { 'application/json': ['.json'] },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const pagesEntry: Record<string, Page>[] = [];
|
||||
pagesEntry['chatPages'] = pages;
|
||||
|
||||
const exportObject = Object.assign(settings, pagesEntry);
|
||||
|
||||
window
|
||||
.showSaveFilePicker(opts)
|
||||
.then((fileHandle) => {
|
||||
fileHandle.createWritable().then((writableHandle) => {
|
||||
writableHandle.write(JSON.stringify(exportObject));
|
||||
writableHandle.close();
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
// Log the error if the error has nothing to do with the user aborting the download
|
||||
if (e.name !== 'AbortError') {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function importChatSettings(settings: string | string[]) {
|
||||
if (Array.isArray(settings)) {
|
||||
return;
|
||||
}
|
||||
const dispatch = useDispatch();
|
||||
const ourImport = JSON.parse(settings);
|
||||
if (!ourImport?.version) {
|
||||
return;
|
||||
}
|
||||
const pageRecord = ourImport['chatPages'];
|
||||
delete ourImport['chatPages'];
|
||||
|
||||
dispatch(importSettings(ourImport, pageRecord));
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
// Components specific to tgui-panel
|
||||
@include meta.load-css(
|
||||
'../components/Chat.scss',
|
||||
$with: ('text-color': #ffffff)
|
||||
$with: ('text-color': hsl(0, 0%, 100%))
|
||||
);
|
||||
|
||||
// Layouts
|
||||
|
||||
Reference in New Issue
Block a user