RichPresence fix

Light theme semi-fix
LightcordApi update
This commit is contained in:
Jean Ouina 2020-07-17 14:54:49 +02:00
parent c96f94b314
commit 7ec315a11f
29 changed files with 1490 additions and 986 deletions

View File

@ -1,21 +1,21 @@
MIT License MIT License
Copyright (c) 2015-present Jiiks | 2017-present Zack Rauen Copyright (c) 2015-present Jiiks | 2017-present Zack Rauen | 2020-present Lightcord
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

View File

@ -952,20 +952,13 @@ body .ace_closeButton:active {
justify-content: space-between; justify-content: space-between;
} }
.theme-dark .ui-tab-bar-header .bd-changelog-button {
color: #72767d;
}
.theme-light .ui-tab-bar-header .bd-changelog-button {
color: #b9bbbe;
}
.ui-tab-bar-header .bd-icon { .ui-tab-bar-header .bd-icon {
cursor: pointer; cursor: pointer;
fill: #72767d; fill: var(--text-muted);
} }
.ui-tab-bar-header .bd-icon:hover { .ui-tab-bar-header .bd-icon:hover {
fill: #fff; fill: var(--text-normal);
} }
.ui-tab-bar-separator { .ui-tab-bar-separator {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,10 +1,11 @@
/* BDEvents */ /* BDEvents */
const EventEmitter = require("events"); const EventEmitter = require("events");
export default new class BDEvents extends EventEmitter {
constructor(){ export default new class BDEvents extends EventEmitter {
super() constructor(){
window.Lightcord.BetterDiscord.BDEvents = this super()
} window.Lightcord.BetterDiscord.BDEvents = this
dispatch(eventName, ...args) {this.emit(eventName, ...args);} }
off(eventName, eventAction) {this.removeListener(eventName, eventAction);} dispatch(eventName, ...args) {this.emit(eventName, ...args);}
off(eventName, eventAction) {this.removeListener(eventName, eventAction);}
}; };

View File

@ -1,149 +1,149 @@
import {bdthemeErrors, themeCookie, settingsCookie, bdthemes} from "../0globals"; import {bdthemeErrors, themeCookie, settingsCookie, bdthemes} from "../0globals";
import ContentManager from "./contentManager"; import ContentManager from "./contentManager";
import DataStore from "./dataStore"; import DataStore from "./dataStore";
import BDEvents from "./bdEvents"; import BDEvents from "./bdEvents";
import Utils from "./utils"; import Utils from "./utils";
import DOM from "./domtools"; import DOM from "./domtools";
class ThemeModule { class ThemeModule {
constructor(){ constructor(){
window.Lightcord.BetterDiscord.ThemeModule = this window.Lightcord.BetterDiscord.ThemeModule = this
} }
get folder() {return ContentManager.themesFolder;} get folder() {return ContentManager.themesFolder;}
} }
ThemeModule.prototype.loadThemes = async function () { ThemeModule.prototype.loadThemes = async function () {
this.loadThemeData(); this.loadThemeData();
bdthemeErrors.splice(0, 0, ...(await ContentManager.loadThemes())); bdthemeErrors.splice(0, 0, ...(await ContentManager.loadThemes()));
const themes = Object.keys(bdthemes); const themes = Object.keys(bdthemes);
for (let i = 0; i < themes.length; i++) { for (let i = 0; i < themes.length; i++) {
const theme = bdthemes[themes[i]]; const theme = bdthemes[themes[i]];
if (!themeCookie[theme.name]) themeCookie[theme.name] = false; if (!themeCookie[theme.name]) themeCookie[theme.name] = false;
if (themeCookie[theme.name]) DOM.addStyle(DOM.escapeID(theme.id), unescape(theme.css)); if (themeCookie[theme.name]) DOM.addStyle(DOM.escapeID(theme.id), unescape(theme.css));
await new Promise((resolve) => setTimeout(resolve, 10)) await new Promise((resolve) => setTimeout(resolve, 10))
} }
for (const theme in themeCookie) { for (const theme in themeCookie) {
if (!bdthemes[theme]) delete themeCookie[theme]; if (!bdthemes[theme]) delete themeCookie[theme];
} }
this.saveThemeData(); this.saveThemeData();
// if (settingsCookie["fork-ps-5"]) ContentManager.watchContent("theme"); // if (settingsCookie["fork-ps-5"]) ContentManager.watchContent("theme");
}; };
ThemeModule.prototype.enableTheme = function(name, reload = false) { ThemeModule.prototype.enableTheme = function(name, reload = false) {
themeCookie[name] = true; themeCookie[name] = true;
this.saveThemeData(); this.saveThemeData();
const theme = bdthemes[name]; const theme = bdthemes[name];
DOM.addStyle(DOM.escapeID(theme.id), unescape(theme.css)); DOM.addStyle(DOM.escapeID(theme.id), unescape(theme.css));
if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${theme.name} v${theme.version} has been applied.`); if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${theme.name} v${theme.version} has been applied.`);
}; };
ThemeModule.prototype.enable = function (name, reload = false) { ThemeModule.prototype.enable = function (name, reload = false) {
return this.enableTheme(name, reload); return this.enableTheme(name, reload);
}; };
ThemeModule.prototype.disableTheme = function(name, reload = false) { ThemeModule.prototype.disableTheme = function(name, reload = false) {
themeCookie[name] = false; themeCookie[name] = false;
this.saveThemeData(); this.saveThemeData();
const theme = bdthemes[name]; const theme = bdthemes[name];
DOM.removeStyle(DOM.escapeID(theme.id)); DOM.removeStyle(DOM.escapeID(theme.id));
if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${theme.name} v${theme.version} has been disabled.`); if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${theme.name} v${theme.version} has been disabled.`);
}; };
ThemeModule.prototype.disable = function (name, reload = false) { ThemeModule.prototype.disable = function (name, reload = false) {
return this.disableTheme(name, reload); return this.disableTheme(name, reload);
}; };
ThemeModule.prototype.toggleTheme = function(theme) { ThemeModule.prototype.toggleTheme = function(theme) {
if (themeCookie[theme]) this.disableTheme(theme); if (themeCookie[theme]) this.disableTheme(theme);
else this.enableTheme(theme); else this.enableTheme(theme);
}; };
ThemeModule.prototype.toggle = function (name, reload = false) { ThemeModule.prototype.toggle = function (name, reload = false) {
return this.toggleTheme(name, reload); return this.toggleTheme(name, reload);
}; };
ThemeModule.prototype.loadTheme = async function(filename) { ThemeModule.prototype.loadTheme = async function(filename) {
const error = await ContentManager.loadContent(filename, "theme"); const error = await ContentManager.loadContent(filename, "theme");
if (error) { if (error) {
if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({themes: [error]}); if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({themes: [error]});
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${filename} could not be loaded. It may not have been loaded.`, {type: "error"}); if (settingsCookie["fork-ps-2"]) Utils.showToast(`${filename} could not be loaded. It may not have been loaded.`, {type: "error"});
return Utils.err("ContentManager", `${filename} could not be loaded.`, error); return Utils.err("ContentManager", `${filename} could not be loaded.`, error);
} }
const theme = Object.values(bdthemes).find(p => p.filename == filename); const theme = Object.values(bdthemes).find(p => p.filename == filename);
Utils.log("ContentManager", `${theme.name} v${theme.version} was loaded.`); Utils.log("ContentManager", `${theme.name} v${theme.version} was loaded.`);
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme.name} v${theme.version} was loaded.`, {type: "success"}); if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme.name} v${theme.version} was loaded.`, {type: "success"});
BDEvents.dispatch("theme-loaded", theme.name); BDEvents.dispatch("theme-loaded", theme.name);
}; };
ThemeModule.prototype.unloadTheme = function(filenameOrName) { ThemeModule.prototype.unloadTheme = function(filenameOrName) {
const bdtheme = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName]; const bdtheme = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName];
if (!bdtheme) return; if (!bdtheme) return;
const theme = bdtheme.name; const theme = bdtheme.name;
if (themeCookie[theme]) this.disableTheme(theme, true); if (themeCookie[theme]) this.disableTheme(theme, true);
const error = ContentManager.unloadContent(bdthemes[theme].filename, "theme"); const error = ContentManager.unloadContent(bdthemes[theme].filename, "theme");
delete bdthemes[theme]; delete bdthemes[theme];
if (error) { if (error) {
if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({themes: [error]}); if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({themes: [error]});
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} could not be unloaded. It may have not been loaded yet.`, {type: "error"}); if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} could not be unloaded. It may have not been loaded yet.`, {type: "error"});
return Utils.err("ContentManager", `${theme} could not be unloaded. It may have not been loaded yet.`, error); return Utils.err("ContentManager", `${theme} could not be unloaded. It may have not been loaded yet.`, error);
} }
Utils.log("ContentManager", `${theme} was unloaded.`); Utils.log("ContentManager", `${theme} was unloaded.`);
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} was unloaded.`, {type: "success"}); if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} was unloaded.`, {type: "success"});
BDEvents.dispatch("theme-unloaded", theme); BDEvents.dispatch("theme-unloaded", theme);
}; };
ThemeModule.prototype.delete = function(filenameOrName) { ThemeModule.prototype.delete = function(filenameOrName) {
const bdplugin = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName]; const bdtheme = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName];
if (!bdplugin) return; if (!bdtheme) return;
this.unloadTheme(bdplugin.filename); this.unloadTheme(bdtheme.filename);
const fullPath = require("path").resolve(ContentManager.pluginsFolder, bdplugin.filename); const fullPath = require("path").resolve(ContentManager.themesFolder, bdtheme.filename);
require("fs").unlinkSync(fullPath); require("fs").unlinkSync(fullPath);
}; };
ThemeModule.prototype.reloadTheme = async function(filenameOrName) { ThemeModule.prototype.reloadTheme = async function(filenameOrName) {
const bdtheme = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName]; const bdtheme = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName];
if (!bdtheme) return this.loadTheme(filenameOrName); if (!bdtheme) return this.loadTheme(filenameOrName);
const theme = bdtheme.name; const theme = bdtheme.name;
const error = await ContentManager.reloadContent(bdthemes[theme].filename, "theme"); const error = await ContentManager.reloadContent(bdthemes[theme].filename, "theme");
if (themeCookie[theme]) this.disableTheme(theme, true), this.enableTheme(theme, true); if (themeCookie[theme]) this.disableTheme(theme, true), this.enableTheme(theme, true);
if (error) { if (error) {
if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({themes: [error]}); if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({themes: [error]});
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} could not be reloaded.`, {type: "error"}); if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} could not be reloaded.`, {type: "error"});
return Utils.err("ContentManager", `${theme} could not be reloaded.`, error); return Utils.err("ContentManager", `${theme} could not be reloaded.`, error);
} }
Utils.log("ContentManager", `${theme} v${bdthemes[theme].version} was reloaded.`); Utils.log("ContentManager", `${theme} v${bdthemes[theme].version} was reloaded.`);
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} v${bdthemes[theme].version} was reloaded.`, {type: "success"}); if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} v${bdthemes[theme].version} was reloaded.`, {type: "success"});
BDEvents.dispatch("theme-reloaded", theme); BDEvents.dispatch("theme-reloaded", theme);
}; };
ThemeModule.prototype.reload = function(name) { ThemeModule.prototype.reload = function(name) {
return this.reloadTheme(name); return this.reloadTheme(name);
}; };
ThemeModule.prototype.edit = function(filenameOrName) { ThemeModule.prototype.edit = function(filenameOrName) {
const bdplugin = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName]; const bdplugin = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName];
if (!bdplugin) return; if (!bdplugin) return;
const fullPath = require("path").resolve(ContentManager.themesFolder, bdplugin.filename); const fullPath = require("path").resolve(ContentManager.themesFolder, bdplugin.filename);
require("electron").shell.openItem(`${fullPath}`); require("electron").shell.openItem(`${fullPath}`);
}; };
ThemeModule.prototype.updateThemeList = function() { ThemeModule.prototype.updateThemeList = function() {
const results = ContentManager.loadNewContent("theme"); const results = ContentManager.loadNewContent("theme");
for (const filename of results.added) this.loadTheme(filename); for (const filename of results.added) this.loadTheme(filename);
for (const name of results.removed) this.unloadTheme(name); for (const name of results.removed) this.unloadTheme(name);
}; };
ThemeModule.prototype.loadThemeData = function() { ThemeModule.prototype.loadThemeData = function() {
const saved = DataStore.getSettingGroup("themes"); const saved = DataStore.getSettingGroup("themes");
if (saved) { if (saved) {
Object.assign(themeCookie, saved); Object.assign(themeCookie, saved);
} }
}; };
ThemeModule.prototype.saveThemeData = function () { ThemeModule.prototype.saveThemeData = function () {
DataStore.setSettingGroup("themes", themeCookie); DataStore.setSettingGroup("themes", themeCookie);
}; };
export default new ThemeModule(); export default new ThemeModule();

View File

@ -1,9 +1,7 @@
import ErrorBoundary from "./errorBoundary"; import ErrorBoundary from "./errorBoundary";
import ContentColumn from "./contentColumn"; import ContentColumn from "./contentColumn";
import Tools from "./tools";
import ReloadIcon from "./reloadIcon"; import ReloadIcon from "./reloadIcon";
import AddonCard from "./addoncard"; import AddonCard from "./addoncard";
import Scroller from "./scroller";
import Dropdown from "./components/dropdown"; import Dropdown from "./components/dropdown";
import Search from "./components/search"; import Search from "./components/search";
@ -15,7 +13,8 @@ import themeModule from "../modules/themeModule";
import WebpackModules from "../modules/webpackModules"; import WebpackModules from "../modules/webpackModules";
import BdApi from "../modules/bdApi"; import BdApi from "../modules/bdApi";
import Utils from "../modules/utils"; import Utils from "../modules/utils";
import tooltipWrap from "./tooltipWrap"; import TooltipWrap from "./tooltipWrap";
import bdEvents from "../modules/bdEvents";
const Tooltip = WebpackModules.findByDisplayName("Tooltip"); const Tooltip = WebpackModules.findByDisplayName("Tooltip");
@ -32,6 +31,25 @@ export default class CardList extends BDV2.reactComponent {
this.sort = this.sort.bind(this); this.sort = this.sort.bind(this);
this.reverse = this.reverse.bind(this); this.reverse = this.reverse.bind(this);
this.search = this.search.bind(this); this.search = this.search.bind(this);
this.onAddonChanges = function(){
this.forceUpdate()
}
this.onAddonChanges = this.onAddonChanges.bind(this)
}
componentDidMount(){
const type = (this.isPlugins ? "plugin" : "theme") + "-"
bdEvents.on(`${type}loaded`, this.onAddonChanges)
bdEvents.on(`${type}unloaded`, this.onAddonChanges)
bdEvents.on(`${type}reloaded`, this.onAddonChanges)
}
componentWillUnmount(){
const type = (this.isPlugins ? "plugin" : "theme") + "-"
bdEvents.off(`${type}loaded`, this.onAddonChanges)
bdEvents.off(`${type}unloaded`, this.onAddonChanges)
bdEvents.off(`${type}reloaded`, this.onAddonChanges)
} }
openFolder() { openFolder() {
@ -154,7 +172,7 @@ export default class CardList extends BDV2.reactComponent {
if(typeof window.PluginUpdates.checkAll !== "function")return null if(typeof window.PluginUpdates.checkAll !== "function")return null
if(!this.isPlugins)return null if(!this.isPlugins)return null
return <tooltipWrap text="Checks for updates of plugins that support this feature. Right-click for a list."> return <TooltipWrap text="Checks for updates of plugins that support this feature. Right-click for a list.">
<span style={{marginLeft: "10px"}}> <span style={{marginLeft: "10px"}}>
<Lightcord.Api.Components.inputs.Button color="brand" look="filled" size="min" hoverColor="default" onClick={() => { <Lightcord.Api.Components.inputs.Button color="brand" look="filled" size="min" hoverColor="default" onClick={() => {
try{ try{
@ -174,7 +192,7 @@ export default class CardList extends BDV2.reactComponent {
Check for Updates Check for Updates
</Lightcord.Api.Components.inputs.Button> </Lightcord.Api.Components.inputs.Button>
</span> </span>
</tooltipWrap> </TooltipWrap>
} }
render() { render() {

View File

@ -1,13 +1,13 @@
import BDV2 from "../../modules/v2"; import BDV2 from "../../modules/v2";
const React = BDV2.React; const React = BDV2.React;
export default class Delete extends React.Component { export default class Delete extends React.Component {
render() { render() {
const size = this.props.size || "24px"; 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}> return <svg className={this.props.className || ""} fill="var(--text-normal)" 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 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"/> <path fill="none" d="M0 0h24v24H0z"/>
</svg>; </svg>;
} }
} }

View File

@ -1,12 +1,12 @@
import BDV2 from "../../modules/v2"; import BDV2 from "../../modules/v2";
const React = BDV2.React; const React = BDV2.React;
export default class DownArrow extends React.Component { export default class DownArrow extends React.Component {
render() { render() {
const size = this.props.size || "16px"; const size = this.props.size || "16px";
return <svg className={this.props.className || ""} fill="#FFFFFF" viewBox="0 0 24 24" style={{width: size, height: size}}> return <svg className={this.props.className || ""} fill="var(--text-normal)" viewBox="0 0 24 24" style={{width: size, height: size}}>
<path d="M8.12 9.29L12 13.17l3.88-3.88c.39-.39 1.02-.39 1.41 0 .39.39.39 1.02 0 1.41l-4.59 4.59c-.39.39-1.02.39-1.41 0L6.7 10.7c-.39-.39-.39-1.02 0-1.41.39-.38 1.03-.39 1.42 0z"/> <path d="M8.12 9.29L12 13.17l3.88-3.88c.39-.39 1.02-.39 1.41 0 .39.39.39 1.02 0 1.41l-4.59 4.59c-.39.39-1.02.39-1.41 0L6.7 10.7c-.39-.39-.39-1.02 0-1.41.39-.38 1.03-.39 1.42 0z"/>
</svg>; </svg>;
} }
} }

View File

@ -1,13 +1,13 @@
import BDV2 from "../../modules/v2"; import BDV2 from "../../modules/v2";
const React = BDV2.React; const React = BDV2.React;
export default class Edit extends React.Component { export default class Edit extends React.Component {
render() { render() {
const size = this.props.size || "24px"; const size = this.props.size || "24px";
return <svg className={this.props.className || ""} viewBox="0 0 24 24" fill="#FFFFFF" style={{width: size, height: size}} onClick={this.props.onClick}> return <svg className={this.props.className || ""} viewBox="0 0 24 24" fill="var(--text-normal)" 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="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" /> <path d="M0 0h24v24H0z" fill="none" />
</svg>; </svg>;
} }
} }

View File

@ -1,18 +1,18 @@
{/* <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18px" height="18px"> {/* <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18px" height="18px">
<path d="M0 0h24v24H0z" fill="none"/> <path d="M0 0h24v24H0z" fill="none"/>
<path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"/> <path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"/>
</svg> */} </svg> */}
import BDV2 from "../../modules/v2"; import BDV2 from "../../modules/v2";
const React = BDV2.React; const React = BDV2.React;
export default class History extends React.Component { export default class History extends React.Component {
render() { render() {
const size = this.props.size || "18px"; const size = this.props.size || "18px";
return <svg viewBox="0 0 24 24" fill="#FFFFFF" className={this.props.className || ""} style={{width: size, height: size}} onClick={this.props.onClick}> return <svg viewBox="0 0 24 24" fill="var(--text-normal)" className={this.props.className || ""} style={{width: size, height: size}} onClick={this.props.onClick}>
<path d="M0 0h24v24H0z" fill="none"/> <path d="M0 0h24v24H0z" fill="none"/>
<path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"/> <path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"/>
</svg>; </svg>;
} }
} }

View File

@ -1,13 +1,13 @@
import BDV2 from "../../modules/v2"; import BDV2 from "../../modules/v2";
const React = BDV2.React; const React = BDV2.React;
export default class Search extends React.Component { export default class Search extends React.Component {
render() { render() {
const size = this.props.size || "16px"; const size = this.props.size || "16px";
return <svg className={this.props.className || ""} fill="#FFFFFF" viewBox="0 0 24 24" style={{width: size, height: size}}> return <svg className={this.props.className || ""} fill="var(--text-normal)" viewBox="0 0 24 24" style={{width: size, height: size}}>
<path fill="none" d="M0 0h24v24H0V0z"/> <path fill="none" d="M0 0h24v24H0V0z"/>
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/> <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
</svg>; </svg>;
} }
} }

View File

@ -7,7 +7,8 @@ import CustomRichPresence from "../modules/CustomRichPresence"
import timestampRender from "./timestampRender" import timestampRender from "./timestampRender"
import { remote } from "electron"; import { remote } from "electron";
import MarginTop from "./margintop"; import MarginTop from "./margintop";
import Utils from "../modules/utils";
import { uuidv4 } from "../modules/distant";
const React = BDV2.React; const React = BDV2.React;
@ -496,16 +497,12 @@ class RpcPreview extends React.Component {
} }
render(){ render(){
let preview = new this.preview({
preview: this
})
preview.setState(this.state.rpc)
return (<div className="lc-tabWrapper"> return (<div className="lc-tabWrapper">
<div className="lc-tabnav" style={{flex: "0 1 auto"}}> <div className="lc-tabnav" style={{flex: "0 1 auto"}}>
<Tab preview={this} title="Full Profile" id="profile"/> <Tab preview={this} title="Full Profile" id="profile"/>
<Tab preview={this} title="User Popout" id="popout"/> <Tab preview={this} title="User Popout" id="popout"/>
</div> </div>
{preview.render()} <PresenceErrorCatcher preview={this.preview} state={this.state.rpc} props={{preview: this}} key={this.state.active} />
</div>) </div>)
} }
@ -550,6 +547,204 @@ class Tab extends React.Component {
} }
} }
let emptyClasses
class PresenceErrorCatcher extends React.Component {
componentDidCatch(err, errInfo){
console.error(err, errInfo)
this.setState({
error: true
})
}
render(){
if(!this.state){
this.state = {
error: false
}
}
if(!this.state.error){
try{
const preview = new this.props.preview(this.props.props)
preview.setState(this.props.state)
return preview.render()
}catch(err){
console.error(err)
this.state.error = true
return this.render()
}
}else{
emptyClasses = emptyClasses || BDV2.WebpackModules.find(e => e.emptyStateImage)
if(!emptyClasses){
Utils.showToast("An error occured. Please check the console for more informations.")
return null
}
return <div style={{
margin: "20px"
}}>
<div style={{
backgroundColor: "var(--background-primary)",
padding: "30px 30px",
borderRadius: "8px"
}} className="lc-tab-box-shadow">
<div className={emptyClasses.emptyStateImage} style={{
marginTop: "20px"
}}>
</div>
<div className={emptyClasses.emptyStateHeader}>An error occured</div>
<p className={emptyClasses.emptyStateSubtext}>
Please check the console for more informations. Join our ­
<a className={`${BDV2.anchorClasses.anchor} ${BDV2.anchorClasses.anchorUnderlineOnHover}`} role="button" tabindex={0} onClick={() => {
BDV2.joinLC()
}}>
support server
</a>
­ for help.
</p>
</div>
</div>
}
}
}
let popoutModules
let UserPopoutComponent
let PopoutProps
class Popout extends React.Component {
get modules(){
return popoutModules || (popoutModules = [
BDV2.WebpackModules.find(e => e.default && e.default.displayName === "FluxContainer(ForwardRef(SubscribeGuildMembersContainer(UserPopout)))"),
BDV2.WebpackModules.find(e => e.default && e.default.getCurrentUser)
])
}
render(){
let [
UserPopout,
userModule
] = this.modules
const user = userModule.default.getCurrentUser()
if(!UserPopoutComponent){
if(!UserPopout)throw new Error(`Couldn't find the UserPopout component.`)
const render1 = new UserPopout.default({userId: user.id, guildId: null, channelId: null, disableUserProfileLink: true}).render()
PopoutProps = render1.props
const render2 = render1.type.render(PopoutProps, null)
const render3 = new render2.type(render2.props).render()
UserPopoutComponent = render3.type
}
if(!UserPopoutComponent)throw new Error(`Couldn't find the UserPopoutComponent component.`)
let data = Object.assign({}, defaultRPC, this.props.preview.props.settings.state.data)
const activity = (function(){
if(!this.game)return null
let game = {
name: this.game.name || defaultRPC.name,
application_id: this.game.application_id || defaultRPC.application_id,
details: this.game.details || undefined,
state: this.game.state || undefined,
timestamps: this.game["timestamps.start"] ? {
start: this.game["timestamps.start"]
} : undefined,
assets: this.game["assets.large"] ? {
large_image: this.game["assets.large"],
small_image: this.game["assets.small"] || undefined
} : undefined,
type: 0
}
return game
}).call({
game: data
})
PopoutProps = new UserPopout.default({userId: user.id, guildId: null, channelId: null, disableUserProfileLink: true}).render().props
const popout = new UserPopoutComponent(Object.assign({}, PopoutProps, {
activity: activity
})).render().props.children // bypass tracking
// remove the stop propagation shit.
const container = <div {...window.Lightcord.Api._.excludeProperties(popout.props, ["onClick", "onContextMenu"])} />
return <div className="lc-userPopout lc-tab-box-shadow">
{container}
</div>
}
}
let profileModules
let UserProfileComponent
let ProfileProps
let connectedProfileStore
class Profile extends React.Component {
get modules(){
return profileModules || (profileModules = [
BDV2.WebpackModules.find(e => e.default && e.default.displayName === "UserProfile"),
BDV2.WebpackModules.find(e => e.default && e.default.getCurrentUser)
])
}
render(){
let [
UserProfile,
userModule
] = this.modules
const user = userModule.default.getCurrentUser()
if(!UserProfileComponent){
const render1 = new UserProfile.default({
user: user
}).render()
connectedProfileStore = render1.type
const render2 = new render1.type(render1.props).render()
const render3 = render2.type.render(render2.props, null)
const render4 = new render3.type(render3.props).render()
UserProfileComponent = render4.type
}
if(!UserProfileComponent)throw new Error(`Couldn't find the UserProfileComponent component.`)
let data = Object.assign({}, defaultRPC, this.props.preview.props.settings.state.data)
const activity = (function(){
if(!this.game)return null
let game = {
name: this.game.name || defaultRPC.name,
application_id: this.game.application_id || defaultRPC.application_id,
details: this.game.details || undefined,
state: this.game.state || undefined,
timestamps: this.game["timestamps.start"] ? {
start: this.game["timestamps.start"]
} : undefined,
assets: this.game["assets.large"] ? {
large_image: this.game["assets.large"],
small_image: this.game["assets.small"] || undefined
} : undefined,
type: 0
}
return game
}).call({
game: data
})
ProfileProps = new connectedProfileStore({
user: user,
close: () => {}
}).render().props
const profile = new UserProfileComponent(Object.assign({}, ProfileProps, {
activity: activity
})).render().props.children // bypass tracking
console.log(profile)
profile.props.style = {
width: "auto"
}
return <div className="lc-tab lc-tab-box-shadow">
{profile}
</div>
}
}
/*
let popoutModule let popoutModule
class Popout extends React.Component { // TODO: Probably use internal Components instead of making it from scratch. class Popout extends React.Component { // TODO: Probably use internal Components instead of making it from scratch.
get modules(){ get modules(){
@ -979,4 +1174,4 @@ class Timestamp extends React.Component {
{this.props.message} {this.props.message}
</div> </div>
} }
} }*/

View File

@ -1,23 +1,23 @@
import BDV2 from "../modules/v2"; import BDV2 from "../modules/v2";
import Tooltip from "./tooltip"; import Tooltip from "./tooltip";
export default class extends React.Component { export default class extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
} }
async componentDidMount() { async componentDidMount() {
const {style = "black", side = "top", text = ""} = this.props; const {style = "black", side = "top", text = ""} = this.props;
this.node = BDV2.reactDom.findDOMNode(this); this.node = BDV2.reactDom.findDOMNode(this);
this.tooltip = new Tooltip(this.node, text, {style, side}); this.tooltip = new Tooltip(this.node, text, {style, side});
} }
componentWillUnmount() { componentWillUnmount() {
this.tooltip.hide(); this.tooltip.hide();
delete this.tooltip; delete this.tooltip;
} }
render() { render() {
return this.props.children; return this.props.children;
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -176,7 +176,7 @@ export default class ColorPicker extends React.PureComponent<ColorPickerProps, {
static help = { static help = {
info: "To convert hex colors to decimal, you can do `Lightcord.Api.Utils.HexColorToDecimal('#yourcolor')` and go back with `Lightcord.Api.Utils.DecimalColorToHex(7506394)`", info: "To convert hex colors to decimal, you can do `Lightcord.Api.Utils.HexColorToDecimal('#yourcolor')` and go back with `Lightcord.Api.Utils.DecimalColorToHex(7506394)`",
warn: "The component may not appear instantly. The component need to be loaded, so you could experience 50-300ms loading time depending on your internet connection." warn: "The component may not appear instantly. The component needs to be loaded, so you could experience 50-300ms loading time depending on your internet connection."
} }
} }
let AllPreviews let AllPreviews

View File

@ -4,6 +4,10 @@ import uuid from "./modules/uuid"
import Utils from "./modules/Utils" import Utils from "./modules/Utils"
import DiscordTools from "./modules/DiscordTools" import DiscordTools from "./modules/DiscordTools"
import * as patchers from "./modules/patchers" import * as patchers from "./modules/patchers"
import excludeProperties from "./modules/excludeProperties"
import cloneNullProto from "./modules/cloneNullProto"
import NOOP from "./modules/noop"
import unfreeze from "./modules/Unfreeze"
patchers.patch() patchers.patch()
const LightcordApi = { const LightcordApi = {
@ -11,7 +15,13 @@ const LightcordApi = {
Components: Components, Components: Components,
uuid: uuid, uuid: uuid,
Utils: Utils, Utils: Utils,
DiscordTools: DiscordTools DiscordTools: DiscordTools,
_: {
excludeProperties: excludeProperties,
cloneNullProto: cloneNullProto,
NOOP: NOOP,
unfreeze: unfreeze
}
} }
declare global { declare global {
@ -47,7 +57,11 @@ export interface LightcordGlobal {
devMode: boolean, devMode: boolean,
callRingingBeat: boolean callRingingBeat: boolean
}, },
Api: LightcordApiGlobal Api: LightcordApiGlobal,
BetterDiscord: {
BdApi: typeof import("@bandagedbd/bdapi").BdApi,
[mod:string]:any
}
} }
/** /**

View File

@ -43,10 +43,15 @@ export default new class DiscordTools {
return notification return notification
} }
playSound(sound:Sound){ createSound(sound:Sound){
soundModule = soundModule || WebpackLoader.findByUniqueProperties(["createSound"]) soundModule = soundModule || WebpackLoader.findByUniqueProperties(["createSound"])
if(!soundModule)throw new WebpackLoaderError("Couldn't find soundModule here.") if(!soundModule)throw new WebpackLoaderError("Couldn't find soundModule here.")
const created = soundModule.createSound(sound) const created = soundModule.createSound(sound)
return created
}
playSound(sound:Sound){
const created = this.createSound(sound)
created.play() created.play()
return created return created
} }
@ -102,6 +107,45 @@ export class Notice extends EventEmitter {
}) })
} }
/**
* Will be called whem the notice is removed.
*/
on(event: "removed", listener: () => void):this
/**
* Will be called when the notice is visible or not.
*/
on(event: "showing", listener: (isShowing:boolean) => void):this
/**
* Will be called when the notice queue changes.
*/
on(event: "index", listener: (index:number) => void):this
on(event: string, listener: (...args:any[]) => void){
return super.on(event, listener)
}
/**
* Will be called whem the notice is removed.
*/
once(event: "removed", listener: () => void):this
/**
* Will be called when the notice is visible or not.
*/
once(event: "showing", listener: (isShowing:boolean) => void):this
/**
* Will be called when the notice queue changes.
*/
once(event: "index", listener: (index:number) => void):this
once(event: string, listener: (...args:any[]) => void){
return super.once(event, listener)
}
off(event: "removed", listener: () => void):this
off(event: "showing", listener: (isShowing:boolean) => void):this
off(event: "index", listener: (index:number) => void):this
off(event: string, listener: (...args:any[]) => void){
return super.off(event, listener)
}
state:{ state:{
removed:boolean, removed:boolean,
showing:boolean, showing:boolean,

View File

@ -38,7 +38,7 @@ export default new class Utils {
} }
FindReact(dom:Element, traverseUp:number = 0):React.Component|React.PureComponent{ FindReact(dom:Element, traverseUp:number = 0):React.Component|React.PureComponent{
/** took from https://stackoverflow.com/questions/29321742/react-getting-a-component-from-a-dom-element-for-debugging */ // taken from https://stackoverflow.com/questions/29321742/react-getting-a-component-from-a-dom-element-for-debugging#39165137
const key = Object.keys(dom).find(key=>key.startsWith("__reactInternalInstance$")); const key = Object.keys(dom).find(key=>key.startsWith("__reactInternalInstance$"));
const domFiber = dom[key]; const domFiber = dom[key];
if (domFiber == null) return null; if (domFiber == null) return null;

View File

@ -47,7 +47,9 @@ export function patch(){
}) })
/** END NOTICE */ /** END NOTICE */
/** START IN-APP NOTIFICATIONS */
//getModule(e => true)
/** END IN-APP NOTIFICATIONS */
} }
function getModule(filter: (mod:any) => boolean):Promise<any>{ function getModule(filter: (mod:any) => boolean):Promise<any>{

View File

@ -3,7 +3,7 @@ const path = require("path")
const terser = require("terser") const terser = require("terser")
const util = require("util") const util = require("util")
const production = true const production = false
let fs = require("fs") let fs = require("fs")

View File

@ -0,0 +1 @@
module.exports = require("@lightcord/api")

View File

@ -0,0 +1,11 @@
{
"name": "@lightcord/components",
"version": "1.0.0",
"description": "Components that can be found inside Ligthcord Api",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Lightcord",
"license": "ISC"
}

View File

@ -1,7 +1,7 @@
{ {
"name": "lightcord", "name": "lightcord",
"version": "0.1.2", "version": "0.1.2",
"description": "", "description": "A simple - customizable - Discord Client.",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"compile": "node compile.js", "compile": "node compile.js",

View File

@ -1,24 +1,25 @@
// bootstrap constants // bootstrap constants
// after startup, these constants will be merged into core module constants // after startup, these constants will be merged into core module constants
// since they are used in both locations (see app/Constants.js) // since they are used in both locations (see app/Constants.js)
import { releaseChannel } from './buildInfo'; import { releaseChannel } from './buildInfo';
import appSettings from './appSettings'; import appSettings from './appSettings';
const pak = require("../package.json") const pak = require("../package.json" as any) as any
import * as path from "path" import * as path from "path"
const settings = appSettings.getSettings(); const settings = appSettings.getSettings();
function capitalizeFirstLetter(s) { function capitalizeFirstLetter(s) {
return s.charAt(0).toUpperCase() + s.slice(1); return s.charAt(0).toUpperCase() + s.slice(1);
} }
export const APP_NAME = 'Lightcord' + (releaseChannel === 'stable' ? '' : capitalizeFirstLetter(releaseChannel)); export const APP_NAME = 'Lightcord' + (releaseChannel === 'stable' ? '' : capitalizeFirstLetter(releaseChannel));
const APP_ID_BASE = 'com.squirrel'; const APP_ID_BASE = 'com.squirrel';
export const APP_ID = `${APP_ID_BASE}.${APP_NAME}.${APP_NAME}`; export const APP_ID = `${APP_ID_BASE}.${APP_NAME}.${APP_NAME}`;
export const API_ENDPOINT = settings.get('API_ENDPOINT') || 'https://discord.com/api'; export const API_ENDPOINT = settings.get('API_ENDPOINT') || 'https://discord.com/api';
export const UPDATE_ENDPOINT = settings.get('UPDATE_ENDPOINT') || API_ENDPOINT; export const UPDATE_ENDPOINT = settings.get('UPDATE_ENDPOINT') || API_ENDPOINT;
export const mainAppDirname = path.join(__dirname, "..") export const mainAppDirname = path.join(__dirname, "..")
export const version:string = pak.version export const version:string = pak.version
export const packageJSON:any = pak

View File

@ -19,6 +19,8 @@ import * as moduleUpdater from "./common/moduleUpdater"
import * as paths from "./common/paths" import * as paths from "./common/paths"
import { create } from "./singleInstance"; import { create } from "./singleInstance";
import * as splashScreen from "./splashScreen" import * as splashScreen from "./splashScreen"
import { join } from "path"
import { homedir } from "os"
if (process.platform === 'linux') { if (process.platform === 'linux') {
// Some people are reporting audio problems on Linux that are fixed by setting // Some people are reporting audio problems on Linux that are fixed by setting
@ -55,6 +57,22 @@ function hasArgvFlag(flag) {
//Transform main thread into async //Transform main thread into async
(async function Main(){ (async function Main(){
await electron.app.whenReady() await electron.app.whenReady()
if(process.argv.includes("--should-create-shortcut")){
console.log(`Creating shortcuts.`)
if(process.platform === "win32"){
electron.shell.writeShortcutLink(join(homedir(), "Desktop", "Lightcord.lnk"), "create", {
"appUserModelId": Constants.APP_ID,
description: Constants.packageJSON.description,
target: process.execPath
})
electron.shell.writeShortcutLink(join(electron.app.getPath("appData"), "Microsoft", "Windows", "Start Menu", "Programs", "Lightcord.lnk"), "create", {
"appUserModelId": Constants.APP_ID,
description: Constants.packageJSON.description,
target: process.execPath
})
}
}
console.log(`Initializing Lightcord.`) console.log(`Initializing Lightcord.`)
console.log(`Version: ${buildInfo.version} console.log(`Version: ${buildInfo.version}
@ -82,14 +100,16 @@ commit: ${buildInfo.commit}`)
coreModule.setMainWindowVisible(!startMinimized) coreModule.setMainWindowVisible(!startMinimized)
}, (args) => { }, (args) => {
if (args != null && args.length > 0 && args[0] === '--squirrel-uninstall') { if(args && args.length > 0){
electron.app.quit(); if(args.length > 0 && args[0] === '--squirrel-uninstall') {
return; electron.app.quit();
} return;
}
if(args.length === 1 && args[0] === "--overlay-host"){ // this is a patch for Lightcord that focus itself
//console.warn("OVERLAY HOST DÉTECTÉ. EVENNEMENT IGNORÉ MAIS POURRAIT CAUSER UN PROBLÈME.") if(args && args.length === 1 && args[0] === "--overlay-host"){ // this is a patch for Lightcord that focus itself
return //console.warn("OVERLAY HOST DÉTECTÉ. EVENNEMENT IGNORÉ MAIS POURRAIT CAUSER UN PROBLÈME.")
return
}
} }
if (coreModule) { if (coreModule) {

View File

@ -1,106 +1,106 @@
import * as electron from "electron" import * as electron from "electron"
import * as net from "net" import * as net from "net"
import * as path from "path" import * as path from "path"
import * as fs from "fs" import * as fs from "fs"
import * as os from "os" import * as os from "os"
import * as buildinfo from "./buildInfo" import * as buildinfo from "./buildInfo"
function deleteSocketFile(socketPath) { function deleteSocketFile(socketPath) {
if (process.platform === 'win32') { if (process.platform === 'win32') {
return; return;
} }
if (fs.existsSync(socketPath)) { if (fs.existsSync(socketPath)) {
try { try {
fs.unlinkSync(socketPath); fs.unlinkSync(socketPath);
} catch (error) { } catch (error) {
// Ignore ENOENT errors in case the file was deleted between the exists // Ignore ENOENT errors in case the file was deleted between the exists
// check and the call to unlink sync. This occurred occasionally on CI // check and the call to unlink sync. This occurred occasionally on CI
// which is why this check is here. // which is why this check is here.
if (error.code !== 'ENOENT') { if (error.code !== 'ENOENT') {
throw error; throw error;
} }
} }
} }
} }
/** /**
* Creates server to listen for additional atom application launches. * Creates server to listen for additional atom application launches.
* *
* You can run the command multiple times, but after the first launch * You can run the command multiple times, but after the first launch
* the other launches will just pass their information to this server and then * the other launches will just pass their information to this server and then
* close immediately. * close immediately.
*/ */
function listenForArgumentsFromNewProcess(socketPath, callback) { function listenForArgumentsFromNewProcess(socketPath, callback) {
deleteSocketFile(socketPath); deleteSocketFile(socketPath);
const server = net.createServer(connection => { const server = net.createServer(connection => {
connection.on('data', data => { connection.on('data', data => {
const args = JSON.parse(data.toString()); const args = JSON.parse(data.toString());
callback(args); callback(args);
}); });
}); });
server.listen(socketPath); server.listen(socketPath);
server.on('error', error => console.error('Application server failed', error)); server.on('error', error => console.error('Application server failed', error));
return server; return server;
} }
function tryStart(socketPath, callback, otherAppFound) { function tryStart(socketPath, callback, otherAppFound) {
// FIXME: Sometimes when socketPath doesn't exist, net.connect would strangely // FIXME: Sometimes when socketPath doesn't exist, net.connect would strangely
// take a few seconds to trigger 'error' event, it could be a bug of node // take a few seconds to trigger 'error' event, it could be a bug of node
// or atom-shell, before it's fixed we check the existence of socketPath to // or atom-shell, before it's fixed we check the existence of socketPath to
// speedup startup. // speedup startup.
if (process.platform !== 'win32' && !fs.existsSync(socketPath)) { if (process.platform !== 'win32' && !fs.existsSync(socketPath)) {
callback(); callback();
return; return;
} }
const client = net.connect({ path: socketPath }, () => { const client = net.connect({ path: socketPath }, () => {
client.write(JSON.stringify(process.argv.slice(1)), () => { client.write(JSON.stringify(process.argv.slice(1)), () => {
client.end(); client.end();
otherAppFound(); otherAppFound();
}); });
}); });
client.on('error', callback); client.on('error', callback);
} }
function makeSocketPath() { function makeSocketPath() {
let name = electron.app.name ? electron.app.name : electron.app.getName(); let name = electron.app.name ? electron.app.name : electron.app.getName();
if (buildinfo.releaseChannel !== 'stable') { if (buildinfo.releaseChannel !== 'stable') {
name += buildinfo.releaseChannel; name += buildinfo.releaseChannel;
} }
if (process.platform === 'win32') { if (process.platform === 'win32') {
return '\\\\.\\pipe\\' + name + '-sock'; return '\\\\.\\pipe\\' + name + '-sock';
} else { } else {
return path.join(os.tmpdir(), name + '.sock'); return path.join(os.tmpdir(), name + '.sock');
} }
} }
export function create(startCallback, newProcessCallback) { export function create(startCallback, newProcessCallback) {
const socketPath = makeSocketPath(); const socketPath = makeSocketPath();
tryStart(socketPath, () => { tryStart(socketPath, () => {
const server = listenForArgumentsFromNewProcess(socketPath, newProcessCallback); const server = listenForArgumentsFromNewProcess(socketPath, newProcessCallback);
electron.app.on('will-quit', () => { electron.app.on('will-quit', () => {
server.close(); server.close();
deleteSocketFile(socketPath); deleteSocketFile(socketPath);
}); });
//@ts-ignore //@ts-ignore
electron.app.on('will-exit', () => { electron.app.on('will-exit', () => {
server.close(); server.close();
deleteSocketFile(socketPath); deleteSocketFile(socketPath);
}); });
startCallback(); startCallback();
}, () => { }, () => {
console.log('Another instance exists. Quitting.'); console.log('Another instance exists. Quitting.');
electron.app.exit(0); electron.app.exit(0);
}); });
} }
export function pipeCommandLineArgs(noOtherAppFoundCallback, otherAppFound) { export function pipeCommandLineArgs(noOtherAppFoundCallback, otherAppFound) {
tryStart(makeSocketPath(), noOtherAppFoundCallback, otherAppFound); tryStart(makeSocketPath(), noOtherAppFoundCallback, otherAppFound);
} }