[MIRROR] Lua UI improvements [MDB IGNORE] (#15561)

* Lua UI improvements (#68887)

Atomized from #68816, with a little addition. Fixes some dumb formatting issues with the lua editor, adds a "jump to bottom" button when viewing the state log, and paginates the state logs.

* Lua UI improvements

Co-authored-by: Y0SH1M4S73R <legoboyo@earthlink.net>
This commit is contained in:
SkyratBot
2022-08-12 14:27:00 -04:00
committed by GitHub
co-authored by Y0SH1M4S73R
parent a5f9f74b4c
commit c13c4b0056
8 changed files with 361 additions and 199 deletions
+2 -4
View File
@@ -2017,8 +2017,6 @@
if(log_index <= state_to_view.log.len)
var/list/log_entry = state_to_view.log[log_index]
if(log_entry["chunk"])
LAZYINITLIST(editor.tgui_shared_states)
editor.tgui_shared_states["viewedChunk"] = json_encode(log_entry["chunk"])
editor.tgui_shared_states["modal"] = json_encode("viewChunk")
editor.force_view_chunk = log_entry["chunk"]
editor.force_modal = "viewChunk"
editor.ui_interact(usr)
editor.tgui_shared_states = null
+33 -8
View File
@@ -7,8 +7,17 @@
/// Arguments for a function call or coroutine resume
var/list/arguments = list()
/// If set, the global table and the
var/show_debug_info = FALSE
/// If set, the global table will not be shown in the lua editor
var/show_global_table = FALSE
/// The log page we are currently on
var/page = 0
/// If set, we will force the editor's modal to be this
var/force_modal
/// If set, we will force the editor to look at this chunk
var/force_view_chunk
/datum/lua_editor/New(state, _quick_log_index)
. = ..()
@@ -39,16 +48,24 @@
/datum/lua_editor/ui_data(mob/user)
var/list/data = list()
data["noStateYet"] = !current_state
data["showDebugInfo"] = show_debug_info
data["showGlobalTable"] = show_global_table
if(current_state)
if(current_state.log && show_debug_info)
data["stateLog"] = kvpify_list(refify_list(current_state.log))
if(current_state.log)
data["stateLog"] = kvpify_list(refify_list(current_state.log.Copy((page*50)+1, min((page+1)*50+1, current_state.log.len+1))))
data["page"] = page
data["pageCount"] = CEILING(current_state.log.len/50, 1)
data["tasks"] = current_state.get_tasks()
if(show_debug_info)
if(show_global_table)
current_state.get_globals()
data["globals"] = kvpify_list(refify_list(current_state.globals))
data["states"] = SSlua.states
data["callArguments"] = kvpify_list(refify_list(arguments))
if(force_modal)
data["forceModal"] = force_modal
force_modal = null
if(force_view_chunk)
data["forceViewChunk"] = force_view_chunk
force_view_chunk = null
return data
/datum/lua_editor/proc/traverse_list(list/path, list/root, traversal_depth_offset = 0)
@@ -96,12 +113,14 @@
LAZYREMOVEASSOC(SSlua.editors, "\ref[current_state]", src)
current_state = new_state
LAZYADDASSOCLIST(SSlua.editors, "\ref[current_state]", src)
page = 0
return TRUE
if("switchState")
var/state_index = params["index"]
LAZYREMOVEASSOC(SSlua.editors, "\ref[current_state]", src)
current_state = SSlua.states[state_index]
LAZYADDASSOCLIST(SSlua.editors, "\ref[current_state]", src)
page = 0
return TRUE
if("runCode")
var/code = params["code"]
@@ -191,8 +210,14 @@
if("clearArgs")
arguments.Cut()
return TRUE
if("toggleShowDebugInfo")
show_debug_info = !show_debug_info
if("toggleShowGlobalTable")
show_global_table = !show_global_table
return TRUE
if("nextPage")
page = min(page+1, CEILING(current_state.log.len/50, 1)-1)
return TRUE
if("previousPage")
page = max(page-1, 0)
return TRUE
/datum/lua_editor/ui_close(mob/user)
+1 -1
View File
@@ -58,7 +58,7 @@ GLOBAL_PROTECT(lua_usr)
break
if(append_to_log)
log += list(weakrefify_list(result))
INVOKE_ASYNC(src, .proc/update_editors)
INVOKE_ASYNC(src, /datum/lua_state.proc/update_editors)
/datum/lua_state/proc/load_script(script)
GLOB.IsLuaCall = TRUE
+5 -1
View File
@@ -66,6 +66,7 @@ export class Section extends Component<SectionProps> {
scrollable,
scrollableHorizontal,
children,
onScroll,
...rest
} = this.props;
const hasTitle = canRender(title) || canRender(buttons);
@@ -89,7 +90,10 @@ export class Section extends Component<SectionProps> {
</div>
)}
<div className="Section__rest">
<div ref={this.scrollableRef} className="Section__content">
<div
ref={this.scrollableRef}
onScroll={onScroll}
className="Section__content">
{children}
</div>
</div>
@@ -9,7 +9,9 @@ export const CallModal = (props, context) => {
const [toCall, setToCall] = useLocalState(context, 'toCallTaskInfo');
const { type, params } = toCall;
return (
<Modal height="600px" width="600px">
<Modal
height={`${window.innerHeight * 0.8}px`}
width={`${window.innerWidth * 0.5}px`}>
<Section
fill
scrollable
@@ -7,7 +7,9 @@ export const ChunkViewModal = (props, context) => {
const [, setModal] = useLocalState(context, 'modal');
const [viewedChunk, setViewedChunk] = useLocalState(context, 'viewedChunk');
return (
<Modal height="600px" width="500px">
<Modal
height={`${window.innerHeight * 0.8}px`}
width={`${window.innerWidth * 0.5}px`}>
<Section
fill
scrollable
@@ -7,7 +7,9 @@ export const StateSelectModal = (props, context) => {
const [input, setInput] = useLocalState(context, 'newStateName', '');
const { states } = data;
return (
<Modal height="400px" width="300px">
<Modal
height={`${window.innerHeight * 0.5}px`}
width={`${window.innerWidth * 0.3}px`}>
<Section
fill
title="States"
+311 -182
View File
@@ -1,5 +1,5 @@
import { useBackend, useLocalState } from '../../backend';
import { Box, Button, Flex, Section, Tabs, TextArea, Modal, Stack } from '../../components';
import { Box, Button, Flex, Section, Tabs, TextArea, Modal, Stack, ProgressBar } from '../../components';
import { Window } from '../../layouts';
import { CallModal } from './CallModal';
import { ChunkViewModal } from './ChunkViewModal';
@@ -9,203 +9,332 @@ import { Log } from './Log';
import { TaskManager } from './TaskManager';
import { sanitizeText } from '../../sanitize';
import { marked } from 'marked';
import { Component, createRef } from 'inferno';
import hljs from 'highlight.js/lib/core';
import lua from 'highlight.js/lib/languages/lua';
hljs.registerLanguage('lua', lua);
export const LuaEditor = (props, context) => {
const { act, data } = useBackend(context);
const { noStateYet, globals, documentation, tasks, showDebugInfo } = data;
const [modal, setModal] = useLocalState(
context,
'modal',
noStateYet ? 'states' : null
);
const [activeTab, setActiveTab] = useLocalState(
context,
'activeTab',
'tasks'
);
const [input, setInput] = useLocalState(context, 'scriptInput', '');
let tabContent;
switch (activeTab) {
case 'globals': {
if (!globals) {
tabContent = (
<h1>
Could not retrieve the global table. Was it corrupted or shadowed?
</h1>
);
} else {
tabContent = (
<ListMapper
list={globals}
skipNulls
vvAct={(path) => act('vvGlobal', { indices: path })}
callType="callFunction"
/>
);
export class LuaEditor extends Component {
constructor(props) {
super(props);
this.sectionRef = createRef();
this.state = {
showJumpToBottomButton: false,
activeTab: 'tasks',
scriptInput: '',
};
this.handleSectionScroll = () => {
const { showJumpToBottomButton } = this.state;
const scrollableCurrent = this.sectionRef.current?.scrollableRef.current;
if (
!showJumpToBottomButton &&
scrollableCurrent?.scrollHeight >
scrollableCurrent?.scrollTop + scrollableCurrent?.clientHeight
) {
this.setState({ showJumpToBottomButton: true });
} else if (
showJumpToBottomButton &&
scrollableCurrent?.scrollTop + scrollableCurrent?.clientHeight >=
scrollableCurrent?.scrollHeight
) {
this.setState({ showJumpToBottomButton: false });
}
break;
}
case 'tasks': {
if (!tasks) {
tabContent = (
<h1>
Could not retrieve task info. Was the global table corrupted or
shadowed?
</h1>
);
} else {
tabContent = <TaskManager />;
}
break;
}
case 'log': {
tabContent = <Log />;
break;
};
window.addEventListener('resize', () =>
this.forceUpdate(this.handleSectionScroll)
);
}
componentDidMount() {
const { data } = useBackend(this.context);
const { forceModal, forceViewChunk } = data;
if (forceModal || forceViewChunk) {
const [, setModal] = useLocalState(this.context, 'modal');
const [, setViewedChunk] = useLocalState(this.context, 'viewedChunk');
setModal(forceModal);
setViewedChunk(forceViewChunk);
}
}
return (
<Window width={1280} height={720}>
<Window.Content>
<Button icon="file" onClick={() => setModal('states')}>
States
</Button>
{noStateYet ? (
<Flex
width="100%"
height="100%"
align="center"
justify="space-around">
<h1>Please select or create a lua state to get started.</h1>
</Flex>
) : (
<Stack height="calc(100% - 16px)">
<Stack.Item grow shrink basis="55%">
<Section
fill
pb="16px"
title="Input"
buttons={
<>
<Button.File
onSelectFiles={(file) => setInput(file)}
accept=".lua,.luau">
Import
</Button.File>
<Button onClick={() => setModal('documentation')}>
Help
</Button>
</>
}>
<TextArea
fluid
width="100%"
height="100%"
value={input}
fontFamily="Consolas"
onInput={(_, value) => setInput(value)}
displayedValue={
<Box
style={{
'pointer-events': 'none',
}}
dangerouslySetInnerHTML={{
__html: hljs.highlight(input, { language: 'lua' })
.value,
}}
/>
componentDidUpdate() {
this.handleSectionScroll();
}
render() {
const { act, data } = useBackend(this.context);
const {
noStateYet,
globals,
documentation,
tasks,
showGlobalTable,
page,
pageCount,
} = data;
const [modal, setModal] = useLocalState(
this.context,
'modal',
noStateYet ? 'states' : null
);
const { activeTab, showJumpToBottomButton, scriptInput } = this.state;
let tabContent;
switch (activeTab) {
case 'globals': {
if (!globals) {
tabContent = (
<h1>
Could not retrieve the global table. Was it corrupted or shadowed?
</h1>
);
} else {
tabContent = (
<ListMapper
list={globals}
skipNulls
vvAct={(path) => act('vvGlobal', { indices: path })}
callType="callFunction"
/>
);
}
break;
}
case 'tasks': {
if (!tasks) {
tabContent = (
<h1>
Could not retrieve task info. Was the global table corrupted or
shadowed?
</h1>
);
} else {
tabContent = <TaskManager />;
}
break;
}
case 'log': {
tabContent = <Log />;
break;
}
}
return (
<Window width={1280} height={720}>
<Window.Content>
<Button icon="file" onClick={() => setModal('states')}>
States
</Button>
{noStateYet ? (
<Flex
width="100%"
height="100%"
align="center"
justify="space-around">
<h1>Please select or create a lua state to get started.</h1>
</Flex>
) : (
<Stack height="calc(100% - 16px)">
<Stack.Item grow shrink basis="55%">
<Section
fill
pb="16px"
title="Input"
buttons={
<>
<Button.File
onSelectFiles={(file) =>
this.setState({ scriptInput: file })
}
accept=".lua,.luau">
Import
</Button.File>
<Button onClick={() => setModal('documentation')}>
Help
</Button>
</>
}>
<TextArea
fluid
width="100%"
height="100%"
value={scriptInput}
fontFamily="Consolas"
onInput={(_, value) =>
this.setState({ scriptInput: value })
}
displayedValue={
<Box
style={{
'pointer-events': 'none',
}}
dangerouslySetInnerHTML={{
__html: hljs.highlight(scriptInput, {
language: 'lua',
}).value,
}}
/>
}
/>
<Button onClick={() => act('runCode', { code: scriptInput })}>
Run
</Button>
</Section>
</Stack.Item>
<Stack.Item grow shrink basis="45%">
<Section
fill
pb="24px"
height={
activeTab === 'log'
? showJumpToBottomButton
? 'calc(100% - 48px)'
: 'calc(100% - 32px)'
: '100%'
}
/>
<Button onClick={() => act('runCode', { code: input })}>
Run
</Button>
</Section>
</Stack.Item>
<Stack.Item grow shrink basis="45%">
<Section fill pb="24px" height="100%" width="100%" buttons>
<Stack justify="space-between">
<Stack.Item>
<Tabs>
{!!showDebugInfo && (
width="100%">
<Stack justify="space-between">
<Stack.Item>
<Tabs>
{!!showGlobalTable && (
<Tabs.Tab
selected={activeTab === 'globals'}
onClick={() => {
this.setState({ activeTab: 'globals' });
}}>
Globals
</Tabs.Tab>
)}
<Tabs.Tab
selected={activeTab === 'globals'}
onClick={() => {
setActiveTab('globals');
}}>
Globals
selected={activeTab === 'tasks'}
onClick={() => this.setState({ activeTab: 'tasks' })}>
Tasks
</Tabs.Tab>
)}
<Tabs.Tab
selected={activeTab === 'tasks'}
onClick={() => setActiveTab('tasks')}>
Tasks
</Tabs.Tab>
{!!showDebugInfo && (
<Tabs.Tab
selected={activeTab === 'log'}
onClick={() => {
setActiveTab('log');
this.setState({ activeTab: 'log' });
setTimeout(this.handleSectionScroll, 0);
}}>
Log
</Tabs.Tab>
</Tabs>
</Stack.Item>
<Stack.Item>
<Button.Checkbox
inline
checked={showGlobalTable}
tooltip="WARNING: Displaying the global table can cause significant lag for the entire server, especially when there is a large number of global variables."
onClick={() => {
if (showGlobalTable && activeTab === 'globals') {
this.setState({ activeTab: 'tasks' });
}
act('toggleShowGlobalTable');
}}>
Show Global Table
</Button.Checkbox>
</Stack.Item>
</Stack>
<Section
ref={this.sectionRef}
fill
scrollable
scrollableHorizontal
onScroll={this.handleSectionScroll}
width="100%">
{tabContent}
</Section>
{activeTab === 'log' && (
<>
<Stack justify="space-between">
<Stack.Item width="25%">
<Button
width="100%"
align="center"
icon="arrow-left"
disabled={page <= 0}
onClick={() => {
act('previousPage');
}}
/>
</Stack.Item>
{!!pageCount && (
<Stack.Item width="50%">
<ProgressBar
width="100%"
value={page / (pageCount - 1)}>
<Box width="100%" align="center">
{`Page ${page + 1}/${pageCount}`}
</Box>
</ProgressBar>
</Stack.Item>
)}
<Stack.Item width="25%">
<Button
width="100%"
align="center"
icon="arrow-right"
disabled={page >= pageCount - 1}
onClick={() => {
act('nextPage');
}}
/>
</Stack.Item>
</Stack>
{showJumpToBottomButton && (
<Button
width="100%"
onClick={() => {
const sectionCurrent = this.sectionRef.current;
const scrollableCurrent =
sectionCurrent.scrollableRef.current;
scrollableCurrent.scrollTop =
scrollableCurrent.scrollHeight;
}}>
Jump to Bottom
</Button>
)}
</Tabs>
</Stack.Item>
<Stack.Item>
<Button.Checkbox
inline
checked={showDebugInfo}
tooltip="WARNING: Enabling debug info can cause significant lag for the entire server, especially when there is a large number of global variables."
onClick={() => {
if (showDebugInfo && activeTab !== 'tasks') {
setActiveTab('tasks');
}
act('toggleShowDebugInfo');
}}>
Show Debug Info
</Button.Checkbox>
</Stack.Item>
</Stack>
<Section fill scrollable scrollableHorizontal width="100%">
{tabContent}
</>
)}
</Section>
</Section>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
)}
</Window.Content>
{modal === 'states' && <StateSelectModal />}
{modal === 'viewChunk' && <ChunkViewModal />}
{modal === 'call' && <CallModal />}
{modal === 'documentation' && (
<Modal>
<Button
color="red"
icon="window-close"
onClick={() => {
setModal(null);
}}>
Close
</Button>
<Section
height={`${window.innerHeight * 0.8}px`}
width={`${window.innerWidth * 0.5}px`}
fill
scrollable>
<Box
dangerouslySetInnerHTML={{
__html: marked(sanitizeText(documentation), {
breaks: true,
smartypants: true,
smartLists: true,
langPrefix: 'hljs language-',
highlight: (code) => {
return hljs.highlight(code, { language: 'lua' }).value;
},
}),
}}
/>
</Section>
</Modal>
)}
</Window.Content>
{modal === 'states' && <StateSelectModal />}
{modal === 'viewChunk' && <ChunkViewModal />}
{modal === 'call' && <CallModal />}
{modal === 'documentation' && (
<Modal>
<Button
color="red"
icon="window-close"
onClick={() => {
setModal(null);
}}>
Close
</Button>
<Section height="500px" width="700px" fill scrollable>
<Box
dangerouslySetInnerHTML={{
__html: marked(sanitizeText(documentation), {
breaks: true,
smartypants: true,
smartLists: true,
langPrefix: 'hljs language-',
highlight: (code) => {
return hljs.highlight(code, { language: 'lua' }).value;
},
}),
}}
/>
</Section>
</Modal>
)}
</Window>
);
};
</Window>
);
}
}