add edit/delete and improve window
This commit is contained in:
parent
0ea223c8f3
commit
3e933923e8
35
css/main.css
35
css/main.css
|
@ -232,15 +232,28 @@
|
|||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.floating-window-buttons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.floating-window-buttons .button {
|
||||
cursor: pointer;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.floating-window-buttons .close-button svg {
|
||||
.floating-window-buttons .button svg {
|
||||
fill: #dcddde;
|
||||
margin-top: 1.5px;
|
||||
}
|
||||
|
||||
.floating-window-buttons .button:hover svg {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.floating-window-buttons .button:hover {
|
||||
background-color: #36393F;
|
||||
}
|
||||
|
||||
.floating-window-buttons .close-button:hover {
|
||||
background-color: #f04747;
|
||||
}
|
||||
|
@ -537,6 +550,19 @@ background-color: #000;
|
|||
}
|
||||
|
||||
|
||||
.bd-addon-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bd-addon-button + .bd-addon-button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* BEGIN EMOTE STYLING */
|
||||
/* =================== */
|
||||
#emote-container {
|
||||
|
@ -1153,6 +1179,13 @@ color: #f6f6f7;
|
|||
max-width: 750px;
|
||||
}
|
||||
|
||||
.floating-addon-window {
|
||||
min-width: 535px;
|
||||
min-height: 605px;
|
||||
max-height: 90%;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
/* Ace Editor Settings */
|
||||
#ace_settingsmenu_container {
|
||||
background: rgba(0,0,0, 0.7)!important;
|
||||
|
|
54
js/main.js
54
js/main.js
File diff suppressed because one or more lines are too long
|
@ -28,10 +28,12 @@ export default [
|
|||
type: "category",
|
||||
id: "addons",
|
||||
collapsible: true,
|
||||
shown: false,
|
||||
settings: [
|
||||
{type: "switch", id: "addonErrors", value: true},
|
||||
{type: "switch", id: "autoScroll", value: true},
|
||||
{type: "switch", id: "autoReload", value: true}
|
||||
{type: "switch", id: "autoReload", value: true},
|
||||
{type: "dropdown", id: "editAction", value: "detached", options: [{value: "detached"}, {value: "system"}]}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -71,6 +71,14 @@ export default {
|
|||
name: "Automatic Loading",
|
||||
note: "Automatically loads, reloads, and unloads plugins and themes"
|
||||
},
|
||||
editAction: {
|
||||
name: "Edit Action",
|
||||
note: "Where plugins & themes appear when editing",
|
||||
options: {
|
||||
detached: "Detached Window",
|
||||
system: "System Editor"
|
||||
}
|
||||
}
|
||||
},
|
||||
customcss: {
|
||||
name: "Custom CSS",
|
||||
|
@ -191,7 +199,11 @@ export default {
|
|||
version: "Version",
|
||||
added: "Date Added",
|
||||
modified: "Date Modified",
|
||||
search: "Search {{type}}"
|
||||
search: "Search {{type}}",
|
||||
editAddon: "Edit",
|
||||
deleteAddon: "Delete",
|
||||
confirmDelete: "Are you sure you want to delete {{name}}?",
|
||||
confirmationText: "You have unsaved changes to {{name}}. Closing this window will lose all those changes.",
|
||||
},
|
||||
Emotes: {
|
||||
loading: "Loading emotes in the background do not reload.",
|
||||
|
@ -225,9 +237,10 @@ export default {
|
|||
query: "for {{query}}"
|
||||
},
|
||||
Modals: {
|
||||
confirmClose: "Are You Sure?",
|
||||
confirmAction: "Are You Sure?",
|
||||
okay: "Okay",
|
||||
cancel: "Cancel",
|
||||
close: "Close",
|
||||
name: "Name",
|
||||
message: "Message",
|
||||
error: "Error",
|
||||
|
|
|
@ -6,13 +6,20 @@ import DataStore from "./datastore";
|
|||
import AddonError from "../structs/addonerror";
|
||||
import MetaError from "../structs/metaerror";
|
||||
import Toasts from "../ui/toasts";
|
||||
import DiscordModules from "./discordmodules";
|
||||
import Strings from "./strings";
|
||||
|
||||
import AddonEditor from "../ui/misc/addoneditor";
|
||||
import FloatingWindowContainer from "../ui/floating/container";
|
||||
|
||||
const React = DiscordModules.React;
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const Module = require("module").Module;
|
||||
Module.globalPaths.push(path.resolve(require("electron").remote.app.getAppPath(), "node_modules"));
|
||||
|
||||
const splitRegex = /[^\S\r\n]*?\n[^\S\r\n]*?\*[^\S\r\n]?/;
|
||||
const splitRegex = /[^\S\r\n]*?\r?\n[^\S\r\n]*?\*[^\S\r\n]?/;
|
||||
const escapedAtRegex = /^\\@/;
|
||||
|
||||
const stripBOM = function(fileContent) {
|
||||
|
@ -28,6 +35,7 @@ export default class AddonManager {
|
|||
get moduleExtension() {return "";}
|
||||
get extension() {return "";}
|
||||
get addonFolder() {return "";}
|
||||
get language() {return "";}
|
||||
get prefix() {return "addon";}
|
||||
get collection() {return "settings";}
|
||||
get category() {return "addons";}
|
||||
|
@ -258,4 +266,60 @@ export default class AddonManager {
|
|||
if (Settings.get(this.collection, this.category, this.id)) this.watchAddons();
|
||||
return errors;
|
||||
}
|
||||
|
||||
deleteAddon(idOrFileOrAddon) {
|
||||
const addon = typeof(idOrFileOrAddon) == "string" ? this.addonList.find(c => c.id == idOrFileOrAddon || c.filename == idOrFileOrAddon) : idOrFileOrAddon;
|
||||
return fs.unlinkSync(path.resolve(this.addonFolder, addon.filename));
|
||||
}
|
||||
|
||||
saveAddon(idOrFileOrAddon, content) {
|
||||
const addon = typeof(idOrFileOrAddon) == "string" ? this.addonList.find(c => c.id == idOrFileOrAddon || c.filename == idOrFileOrAddon) : idOrFileOrAddon;
|
||||
return fs.writeFileSync(path.resolve(this.addonFolder, addon.filename), content);
|
||||
}
|
||||
|
||||
editAddon(idOrFileOrAddon, system) {
|
||||
const addon = typeof(idOrFileOrAddon) == "string" ? this.addonList.find(c => c.id == idOrFileOrAddon || c.filename == idOrFileOrAddon) : idOrFileOrAddon;
|
||||
const fullPath = path.resolve(this.addonFolder, addon.filename);
|
||||
if (typeof(system) == "undefined") system = Settings.get("settings", "addons", "editAction") == "system";
|
||||
if (system) return require("electron").shell.openItem(`${fullPath}`);
|
||||
return this.openDetached(addon);
|
||||
}
|
||||
|
||||
openDetached(addon) {
|
||||
const fullPath = path.resolve(this.addonFolder, addon.filename);
|
||||
const content = fs.readFileSync(fullPath).toString();
|
||||
|
||||
const editorRef = React.createRef();
|
||||
const editor = React.createElement(AddonEditor, {
|
||||
id: "bd-floating-editor-" + addon.name,
|
||||
ref: editorRef,
|
||||
content: content,
|
||||
save: this.saveAddon.bind(this, addon),
|
||||
openNative: this.editAddon.bind(this, addon, true),
|
||||
language: this.language
|
||||
});
|
||||
|
||||
FloatingWindowContainer.open({
|
||||
onClose: () => {
|
||||
this.isDetached = false;
|
||||
},
|
||||
onResize: () => {
|
||||
if (!editorRef || !editorRef.current || !editorRef.current.resize) return;
|
||||
editorRef.current.resize();
|
||||
},
|
||||
title: addon.name,
|
||||
id: content.id,
|
||||
className: "floating-addon-window",
|
||||
height: 470,
|
||||
width: 410,
|
||||
center: true,
|
||||
resizable: true,
|
||||
children: editor,
|
||||
confirmClose: () => {
|
||||
if (!editorRef || !editorRef.current) return false;
|
||||
return editorRef.current.hasUnsavedChanges;
|
||||
},
|
||||
confirmationText: Strings.Addons.confirmationText.format({name: addon.name})
|
||||
});
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ export default new class PluginManager extends AddonManager {
|
|||
get extension() {return ".plugin.js";}
|
||||
get addonFolder() {return path.resolve(Config.dataPath, "plugins");}
|
||||
get prefix() {return "plugin";}
|
||||
get language() {return "javascript";}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -37,7 +38,11 @@ export default new class PluginManager extends AddonManager {
|
|||
folder: this.addonFolder,
|
||||
onChange: this.togglePlugin.bind(this),
|
||||
reload: this.reloadPlugin.bind(this),
|
||||
refreshList: this.updatePluginList.bind(this)
|
||||
refreshList: this.updatePluginList.bind(this),
|
||||
saveAddon: this.saveAddon.bind(this),
|
||||
editAddon: this.editAddon.bind(this),
|
||||
deleteAddon: this.deleteAddon.bind(this),
|
||||
prefix: this.prefix
|
||||
})});
|
||||
return errors;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ export default new class ThemeManager extends AddonManager {
|
|||
get extension() {return ".theme.css";}
|
||||
get addonFolder() {return path.resolve(Config.dataPath, "themes");}
|
||||
get prefix() {return "theme";}
|
||||
get language() {return "css";}
|
||||
|
||||
initialize() {
|
||||
const errors = super.initialize();
|
||||
|
@ -23,7 +24,11 @@ export default new class ThemeManager extends AddonManager {
|
|||
folder: this.addonFolder,
|
||||
onChange: this.toggleTheme.bind(this),
|
||||
reload: this.reloadTheme.bind(this),
|
||||
refreshList: this.updateThemeList.bind(this)
|
||||
refreshList: this.updateThemeList.bind(this),
|
||||
saveAddon: this.saveAddon.bind(this),
|
||||
editAddon: this.editAddon.bind(this),
|
||||
deleteAddon: this.deleteAddon.bind(this),
|
||||
prefix: this.prefix
|
||||
})});
|
||||
return errors;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import {React, Strings} from "modules";
|
|||
|
||||
import Screen from "../../structs/screen";
|
||||
import CloseButton from "../icons/close";
|
||||
import MaximizeIcon from "../icons/fullscreen";
|
||||
import Modals from "../modals";
|
||||
|
||||
export default class FloatingWindow extends React.Component {
|
||||
|
@ -18,6 +19,7 @@ export default class FloatingWindow extends React.Component {
|
|||
this.window = React.createRef();
|
||||
|
||||
this.close = this.close.bind(this);
|
||||
this.maximize = this.maximize.bind(this);
|
||||
this.onDrag = this.onDrag.bind(this);
|
||||
this.onDragStart = this.onDragStart.bind(this);
|
||||
this.onDragStop = this.onDragStop.bind(this);
|
||||
|
@ -55,7 +57,6 @@ export default class FloatingWindow extends React.Component {
|
|||
|
||||
onDrag(e) {
|
||||
const div = this.window.current;
|
||||
div.style.position = "fixed";
|
||||
div.style.top = (e.clientY - this.offY) + "px";
|
||||
div.style.left = (e.clientX - this.offX) + "px";
|
||||
}
|
||||
|
@ -75,6 +76,9 @@ export default class FloatingWindow extends React.Component {
|
|||
<div className="floating-window-titlebar" ref={this.titlebar}>
|
||||
<span className="title">{this.props.title}</span>
|
||||
<div className="floating-window-buttons">
|
||||
<div className="button maximize-button" onClick={this.maximize}>
|
||||
<MaximizeIcon size="18px" />
|
||||
</div>
|
||||
<div className="button close-button" onClick={this.close}>
|
||||
<CloseButton />
|
||||
</div>
|
||||
|
@ -97,11 +101,19 @@ export default class FloatingWindow extends React.Component {
|
|||
if (this.props.close && shouldClose) this.props.close();
|
||||
}
|
||||
|
||||
maximize() {
|
||||
this.window.current.style.width = "100%";
|
||||
this.window.current.style.height = "100%";
|
||||
this.window.current.style.top = "20px";
|
||||
this.window.current.style.left = "0px";
|
||||
if (this.props.onResize) this.props.onResize();
|
||||
}
|
||||
|
||||
confirmClose() {
|
||||
return new Promise(resolve => {
|
||||
Modals.showConfirmationModal(Strings.Modals.confirmClose, this.props.confirmationText, {
|
||||
Modals.showConfirmationModal(Strings.Modals.confirmAction, this.props.confirmationText, {
|
||||
danger: true,
|
||||
confirmText: "Close",
|
||||
confirmText: Strings.Modals.close,
|
||||
onConfirm: () => {resolve(true);},
|
||||
onCancel: () => {resolve(false);}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import {React} from "modules";
|
||||
|
||||
export default class Delete extends React.Component {
|
||||
render() {
|
||||
const size = this.props.size || "24px";
|
||||
return <svg className={this.props.className || ""} fill="#FFFFFF" viewBox="0 0 24 24" style={{width: size, height: size}} onClick={this.props.onClick}>
|
||||
<path fill="none" d="M0 0h24v24H0V0z"/><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zm2.46-7.12l1.41-1.41L12 12.59l2.12-2.12 1.41 1.41L13.41 14l2.12 2.12-1.41 1.41L12 15.41l-2.12 2.12-1.41-1.41L10.59 14l-2.13-2.12zM15.5 4l-1-1h-5l-1 1H5v2h14V4z"/>
|
||||
<path fill="none" d="M0 0h24v24H0z"/>
|
||||
</svg>;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ import {React} from "modules";
|
|||
export default class Edit extends React.Component {
|
||||
render() {
|
||||
const size = this.props.size || "24px";
|
||||
return <svg viewBox="0 0 24 24" style={{width: size, height: size}}>
|
||||
return <svg viewBox="0 0 24 24" fill="#FFFFFF" style={{width: size, height: size}} onClick={this.props.onClick}>
|
||||
<path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
</svg>;
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import {React} from "modules";
|
||||
|
||||
export default class FullScreen extends React.Component {
|
||||
render() {
|
||||
const size = this.props.size || "24px";
|
||||
return <svg className={this.props.className || ""} fill="#FFFFFF" viewBox="0 0 24 24" style={{width: size, height: size}} onClick={this.props.onClick}>
|
||||
<path fill="none" d="M0 0h24v24H0V0z"/>
|
||||
<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
|
||||
</svg>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import {React, Strings} from "modules";
|
||||
|
||||
import Editor from "../customcss/editor";
|
||||
import Save from "../icons/save";
|
||||
import Edit from "../icons/edit";
|
||||
import Cog from "../icons/cog";
|
||||
|
||||
export default class AddonEditor extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.hasUnsavedChanges = false;
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.save = this.save.bind(this);
|
||||
this.openNative = this.openNative.bind(this);
|
||||
this.update = this.update.bind(this);
|
||||
|
||||
this.controls = [
|
||||
{label: React.createElement(Save, {size: "18px"}), tooltip: Strings.CustomCSS.save, onClick: this.save},
|
||||
{label: React.createElement(Edit, {size: "18px"}), tooltip: Strings.CustomCSS.openNative, onClick: this.openNative},
|
||||
{label: React.createElement(Cog, {size: "18px"}), tooltip: Strings.CustomCSS.settings, onClick: "showSettings"}
|
||||
];
|
||||
}
|
||||
|
||||
update() {
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
updateEditor(newCSS) {
|
||||
if (!this.editor) return;
|
||||
this.editor.value = newCSS;
|
||||
}
|
||||
|
||||
get value() {return this.editor.session.getValue();}
|
||||
set value(newValue) {
|
||||
this.editor.setValue(newValue);
|
||||
}
|
||||
|
||||
showSettings() {return this.editor.keyBinding.$defaultHandler.commands.showSettingsMenu.exec(this.editor);}
|
||||
resize() {return this.editor.resize();}
|
||||
|
||||
setEditorRef(editor) {
|
||||
this.editor = editor;
|
||||
if (this.props.editorRef && typeof(this.props.editorRef.current) !== "undefined") this.props.editorRef.current = editor;
|
||||
else if (this.props.editorRef) this.props.editorRef = editor;
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Editor ref={this.setEditorRef.bind(this)} language={this.props.language} id={this.props.id || "bd-addon-editor"} controls={this.controls} value={this.props.content} onChange={this.onChange} />;
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.hasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
save(event, content) {
|
||||
this.hasUnsavedChanges = false;
|
||||
if (this.props.save) this.props.save(content);
|
||||
}
|
||||
|
||||
openNative() {
|
||||
if (this.props.openNative) this.props.openNative();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
import {React, Logger, Strings} from "modules";
|
||||
import CloseButton from "../icons/close";
|
||||
import ReloadIcon from "../icons/reload";
|
||||
import EditIcon from "../icons/edit";
|
||||
import DeleteIcon from "../icons/delete";
|
||||
import Switch from "./components/switch";
|
||||
|
||||
export default class AddonCard extends React.Component {
|
||||
|
@ -119,7 +121,9 @@ export default class AddonCard extends React.Component {
|
|||
<div className="bd-addon-header">
|
||||
<span className="bd-title">{this.buildTitle(name, version, author)}</span>
|
||||
<div className="bd-controls">
|
||||
{this.props.showReloadIcon && <ReloadIcon className="bd-reload bd-reload-card" onClick={this.reload} />}
|
||||
{this.props.editAddon && <div className="bd-addon-button" onClick={this.props.editAddon}><EditIcon /></div>}
|
||||
{this.props.deleteAddon && <div className="bd-addon-button" onClick={this.props.deleteAddon}><DeleteIcon /></div>}
|
||||
{this.props.showReloadIcon && <div className="bd-addon-button" onClick={this.reload}><ReloadIcon className="bd-reload bd-reload-card" /></div>}
|
||||
<Switch checked={this.props.enabled} onChange={this.onChange} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {React, Settings, Strings} from "modules";
|
||||
import {React, Settings, Strings, Events} from "modules";
|
||||
|
||||
import Modals from "../modals";
|
||||
import SettingsTitle from "./title";
|
||||
import ReloadIcon from "../icons/reload";
|
||||
import AddonCard from "./addoncard";
|
||||
|
@ -14,6 +15,21 @@ export default class AddonList extends React.Component {
|
|||
this.sort = this.sort.bind(this);
|
||||
this.reverse = this.reverse.bind(this);
|
||||
this.search = this.search.bind(this);
|
||||
this.update = this.update.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
Events.on(`${this.props.prefix}-loaded`, this.update);
|
||||
Events.on(`${this.props.prefix}-unloaded`, this.update);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
Events.off(`${this.props.prefix}-loaded`, this.update);
|
||||
Events.off(`${this.props.prefix}-unloaded`, this.update);
|
||||
}
|
||||
|
||||
update() {
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
reload() {
|
||||
|
@ -89,9 +105,31 @@ export default class AddonList extends React.Component {
|
|||
}
|
||||
const hasSettings = addon.type && typeof(addon.plugin.getSettingsPanel) === "function";
|
||||
const getSettings = hasSettings && addon.plugin.getSettingsPanel.bind(addon.plugin);
|
||||
return <AddonCard showReloadIcon={showReloadIcon} key={addon.id} enabled={addonState[addon.id]} addon={addon} onChange={onChange} reload={reload} hasSettings={hasSettings} getSettingsPanel={getSettings} />;
|
||||
return <AddonCard editAddon={this.editAddon.bind(this, addon.id)} deleteAddon={this.deleteAddon.bind(this, addon.id)} showReloadIcon={showReloadIcon} key={addon.id} enabled={addonState[addon.id]} addon={addon} onChange={onChange} reload={reload} hasSettings={hasSettings} getSettingsPanel={getSettings} />;
|
||||
})}
|
||||
</div>
|
||||
];
|
||||
}
|
||||
|
||||
editAddon(id) {
|
||||
if (this.props.editAddon) this.props.editAddon(id);
|
||||
}
|
||||
|
||||
async deleteAddon(id) {
|
||||
const addon = this.props.addonList.find(a => a.id == id);
|
||||
const shouldDelete = await this.confirmDelete(addon);
|
||||
if (!shouldDelete) return;
|
||||
if (this.props.deleteAddon) this.props.deleteAddon(addon);
|
||||
}
|
||||
|
||||
confirmDelete(addon) {
|
||||
return new Promise(resolve => {
|
||||
Modals.showConfirmationModal(Strings.Modals.confirmAction, Strings.Addons.confirmDelete.format({name: addon.name}), {
|
||||
danger: true,
|
||||
confirmText: Strings.Addons.deleteAddon,
|
||||
onConfirm: () => {resolve(true);},
|
||||
onCancel: () => {resolve(false);}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue