提交 01a6a6fb 编写于 作者: R rsercano

electron packaging improvements

上级 123c88cd
.DS_Store
.dist
app
bin
db
node_modules
\ No newline at end of file
electrify package -- --icon=public/logo/head.ico
#mac
electrify package -- --icon=public/logo/head.icns
{
"plugins": []
}
\ No newline at end of file
const {app, BrowserWindow} = require('electron');
const electrify = require('electrify')(__dirname);
// UNCOMMENT FOR MAC PACKAGE
//var Menu = require("menu");
var window = null;
app.on('ready', function () {
// electrify start
electrify.start(function (meteor_root_url) {
// creates a new electron window
window = new BrowserWindow({
width: 1200, height: 900,
'node-integration': false // node integration must to be off
});
// open up meteor root url
window.loadURL(meteor_root_url);
/* UNCOMMENT FOR MAC PACKAGE
var template = [{
label: "Application",
submenu: [
{
label: "Quit", accelerator: "Command+Q", click: function () {
app.quit();
}
}
]
}, {
label: "Edit",
submenu: [
{label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:"},
{label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:"},
{type: "separator"},
{label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:"},
{label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:"},
{label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:"},
{label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:"}
]
}
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));*/
});
});
app.on('window-all-closed', function () {
app.quit();
});
app.on('will-quit', function terminate_and_quit(event) {
// if electrify is up, cancel exiting with `preventDefault`,
// so we can terminate electrify gracefully without leaving child
// processes hanging in background
if (electrify.isup() && event) {
// holds electron termination
event.preventDefault();
// gracefully stops electrify
electrify.stop(function () {
// and then finally quit app
app.quit();
});
}
});
//
// =============================================================================
//
// the methods bellow can be called seamlessly from your Meteor's
// client and server code, using:
//
// Electrify.call('methodname', [..args..], callback);
//
// ATENTION:
// From meteor, you can only call these methods after electrify is fully
// started, use the Electrify.startup() convenience method for this
//
//
// Electrify.startup(function(){
// Electrify.call(...);
// });
//
// =============================================================================
//
// electrify.methods({
// 'method.name': function(name, done) {
// // do things... and call done(err, arg1, ..., argN)
// done(null);
// }
// });
//
const {app, BrowserWindow, Menu} = require('electron');
const path = require('path');
const net = require('net');
const join = path.join;
const spawn = require('child_process').spawn;
const shell = require('shelljs');
const logTag = '[MONGOCLIENT]';
let window = null;
const createWindow = function () {
console.log(logTag, 'trying to start Mongoclient electron application');
const appRoot = path.resolve(__dirname);
// show loading
const win = new BrowserWindow({
width: 1200,
height: 900,
frame: false
});
win.loadURL(join(appRoot, 'loading.html'));
//fix tunnel-ssh
shell.cp('-R', join(appRoot, 'app', '/programs/server/npm/node_modules/tunnel-ssh'), join(appRoot, 'app', 'programs/server/npm/node_modules/meteor/modules-runtime/node_modules/'));
beginStartingMongo(appRoot, win);
};
const beginStartingMongo = function (appRoot, loadingWin) {
console.log(logTag, 'trying to start mongod process');
let path = join(appRoot, 'bin', 'mongod');
if (process.platform === 'win32') {
path += '.exe';
}
console.log(logTag, 'detected mongod executable path: ' + path);
let dataDir;
let lockfile;
if (process.platform === 'win32') {
dataDir = process.env.APPDATA;
}
else if (process.platform === 'darwin') {
dataDir = join(process.env.HOME, 'Library', 'Preferences');
}
else if (process.platform === 'linux') {
dataDir = join(process.env.HOME, 'var', 'local');
}
dataDir = join(dataDir, 'Mongoclient', 'db');
lockfile = join(dataDir, 'mongod.lock');
console.log(logTag, 'detected mongod data directory: ' + dataDir);
console.log(logTag, 'trying to create data dir and removing mongod.lock just in case');
shell.mkdir('-p', dataDir);
shell.rm('-f', lockfile);
freeport(null, function (port) {
console.log(logTag, 'trying to spawn mongod process with port: ' + port);
let mongoProcess = spawn(path, [
'--dbpath', dataDir,
'--port', port,
'--bind_ip', '127.0.0.1',
'--smallfiles'
]);
mongoProcess.stdout.on('data', function (data) {
console.log(logTag, '[MONGOD-STDOUT]', data.toString());
});
mongoProcess.stderr.on('data', function (data) {
console.error(logTag, '[MONGOD-STDERR]', data.toString());
});
mongoProcess.on('exit', function (code) {
console.log(logTag, '[MONGOD-EXIT]', code.toString());
});
startNode(appRoot, port, loadingWin);
});
};
const startNode = function (appRoot, mongoPort, loadingWin) {
console.log(logTag, 'trying to start node process');
let path = join(appRoot, 'bin', 'node');
if (process.platform === 'win32') {
path += '.exe';
}
console.log(logTag, 'detected node executable path: ' + path);
const mainPath = join(appRoot, 'app', 'main.js');
console.log(logTag, 'detected main app root: ' + mainPath);
freeport(null, function (port) {
process.env.PORT = port;
process.env.ROOT_URL = 'http://localhost:' + port;
process.env.MONGO_URL = 'mongodb://localhost:' + mongoPort + '/mongoclient';
console.log(logTag, 'detected environment variables: ' + JSON.stringify(process.env));
console.log(logTag, 'trying to spawn node process with port: ' + port);
let nodeProcess = spawn(path, [mainPath]);
nodeProcess.stdout.on('data', function (data) {
console.log(logTag, '[NODE-STDOUT]', data.toString());
});
nodeProcess.stderr.on('data', function (data) {
console.error(logTag, '[NODE-STDERR]', data.toString());
});
nodeProcess.on('exit', function (code) {
console.log(logTag, '[NODE-EXIT]', code.toString());
});
loadWindow(port, loadingWin);
});
};
const freeport = function (start, done) {
console.log(logTag, 'trying to find free port for spawn');
start = start || 11235;
const socket = new net.Socket()
.once('connect', function () {
socket.destroy();
freeport(++start, done);
})
.once('error', function (/* err */) {
socket.destroy();
done(start);
})
.connect(start, '127.0.0.1');
};
const loadWindow = function (appPort, loadingWin) {
window = new BrowserWindow({
devTools: true,
webPreferences: {
nodeIntegration: false
},
width: 1200,
height: 900
});
window.loadURL("http://localhost:" + appPort);
loadingWin.close();
};
app.on('ready', createWindow);
app.on('activate', () => {
if (window === null) {
createWindow();
}
});
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') {
app.quit()
}
});
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<link href="./loading/spinkit.css" rel="stylesheet">
<link href="./loading/please-wait.css" rel="stylesheet">
<style>
.sk-circle {
margin: 100px auto;
width: 230px;
height: 230px;
position: relative;
}
.sk-circle .sk-child {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
.sk-circle .sk-child:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: white;
border-radius: 100%;
-webkit-animation: sk-circleBounceDelay 1.2s infinite ease-in-out both;
animation: sk-circleBounceDelay 1.2s infinite ease-in-out both;
}
.sk-circle .sk-circle2 {
-webkit-transform: rotate(30deg);
-ms-transform: rotate(30deg);
transform: rotate(30deg); }
.sk-circle .sk-circle3 {
-webkit-transform: rotate(60deg);
-ms-transform: rotate(60deg);
transform: rotate(60deg); }
.sk-circle .sk-circle4 {
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg); }
.sk-circle .sk-circle5 {
-webkit-transform: rotate(120deg);
-ms-transform: rotate(120deg);
transform: rotate(120deg); }
.sk-circle .sk-circle6 {
-webkit-transform: rotate(150deg);
-ms-transform: rotate(150deg);
transform: rotate(150deg); }
.sk-circle .sk-circle7 {
-webkit-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg); }
.sk-circle .sk-circle8 {
-webkit-transform: rotate(210deg);
-ms-transform: rotate(210deg);
transform: rotate(210deg); }
.sk-circle .sk-circle9 {
-webkit-transform: rotate(240deg);
-ms-transform: rotate(240deg);
transform: rotate(240deg); }
.sk-circle .sk-circle10 {
-webkit-transform: rotate(270deg);
-ms-transform: rotate(270deg);
transform: rotate(270deg); }
.sk-circle .sk-circle11 {
-webkit-transform: rotate(300deg);
-ms-transform: rotate(300deg);
transform: rotate(300deg); }
.sk-circle .sk-circle12 {
-webkit-transform: rotate(330deg);
-ms-transform: rotate(330deg);
transform: rotate(330deg); }
.sk-circle .sk-circle2:before {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s; }
.sk-circle .sk-circle3:before {
-webkit-animation-delay: -1s;
animation-delay: -1s; }
.sk-circle .sk-circle4:before {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s; }
.sk-circle .sk-circle5:before {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s; }
.sk-circle .sk-circle6:before {
-webkit-animation-delay: -0.7s;
animation-delay: -0.7s; }
.sk-circle .sk-circle7:before {
-webkit-animation-delay: -0.6s;
animation-delay: -0.6s; }
.sk-circle .sk-circle8:before {
-webkit-animation-delay: -0.5s;
animation-delay: -0.5s; }
.sk-circle .sk-circle9:before {
-webkit-animation-delay: -0.4s;
animation-delay: -0.4s; }
.sk-circle .sk-circle10:before {
-webkit-animation-delay: -0.3s;
animation-delay: -0.3s; }
.sk-circle .sk-circle11:before {
-webkit-animation-delay: -0.2s;
animation-delay: -0.2s; }
.sk-circle .sk-circle12:before {
-webkit-animation-delay: -0.1s;
animation-delay: -0.1s; }
@-webkit-keyframes sk-circleBounceDelay {
0%, 80%, 100% {
-webkit-transform: scale(0);
transform: scale(0);
} 40% {
-webkit-transform: scale(1);
transform: scale(1);
}
}
@keyframes sk-circleBounceDelay {
0%, 80%, 100% {
-webkit-transform: scale(0);
transform: scale(0);
} 40% {
-webkit-transform: scale(1);
transform: scale(1);
}
}
</style>
</head>
<body>
<div class="inner" ng-view>
</div>
<script type="text/javascript" src="./loading/please-wait.min.js"></script>
<script type="text/javascript">
pleaseWait({
logo: "",
backgroundColor: '#2F4050',
loadingHtml: "<div class='sk-circle'><div class='sk-circle1 sk-child'></div><div class='sk-circle2 sk-child'></div><div class='sk-circle3 sk-child'></div><div class='sk-circle4 sk-child'></div><div class='sk-circle5 sk-child'></div><div class='sk-circle6 sk-child'></div><div class='sk-circle7 sk-child'></div><div class='sk-circle8 sk-child'></div><div class='sk-circle9 sk-child'></div><div class='sk-circle10 sk-child'></div><div class='sk-circle11 sk-child'></div><div class='sk-circle12 sk-child'></div></div>"
});
</script>
</body>
</html>
\ No newline at end of file
/* line 17, ../src/please-wait.scss */
body.pg-loading {
overflow: hidden;
}
/* line 21, ../src/please-wait.scss */
.pg-loading-screen {
position: fixed;
bottom: 0;
left: 0;
right: 0;
top: 0;
z-index: 1000000;
opacity: 1;
background-color: #FFF;
-webkit-transition: background-color 0.4s ease-in-out 0s;
-moz-transition: background-color 0.4s ease-in-out 0s;
-ms-transition: background-color 0.4s ease-in-out 0s;
-o-transition: background-color 0.4s ease-in-out 0s;
transition: background-color 0.4s ease-in-out 0s;
}
/* line 32, ../src/please-wait.scss */
.pg-loading-screen.pg-loaded {
opacity: 0;
-webkit-animation: pgAnimLoaded 0.5s cubic-bezier(0.7, 0, 0.3, 1) both;
-moz-animation: pgAnimLoaded 0.5s cubic-bezier(0.7, 0, 0.3, 1) both;
-ms-animation: pgAnimLoaded 0.5s cubic-bezier(0.7, 0, 0.3, 1) both;
-o-animation: pgAnimLoaded 0.5s cubic-bezier(0.7, 0, 0.3, 1) both;
animation: pgAnimLoaded 0.5s cubic-bezier(0.7, 0, 0.3, 1) both;
}
/* line 38, ../src/please-wait.scss */
.pg-loading-screen.pg-loading .pg-loading-logo-header, .pg-loading-screen.pg-loading .pg-loading-html {
opacity: 1;
}
/* line 42, ../src/please-wait.scss */
.pg-loading-screen.pg-loading .pg-loading-logo-header, .pg-loading-screen.pg-loading .pg-loading-html:not(.pg-loaded) {
-webkit-animation: pgAnimLoading 1s cubic-bezier(0.7, 0, 0.3, 1) both;
-moz-animation: pgAnimLoading 1s cubic-bezier(0.7, 0, 0.3, 1) both;
-ms-animation: pgAnimLoading 1s cubic-bezier(0.7, 0, 0.3, 1) both;
-o-animation: pgAnimLoading 1s cubic-bezier(0.7, 0, 0.3, 1) both;
animation: pgAnimLoading 1s cubic-bezier(0.7, 0, 0.3, 1) both;
}
/* line 46, ../src/please-wait.scss */
.pg-loading-screen.pg-loading .pg-loading-html:not(.pg-loaded) {
-webkit-animation-delay: 0.3s;
-moz-animation-delay: 0.3s;
-ms-animation-delay: 0.3s;
-o-animation-delay: 0.3s;
animation-delay: 0.3s;
}
/* line 51, ../src/please-wait.scss */
.pg-loading-screen .pg-loading-inner {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
position: static;
}
/* line 59, ../src/please-wait.scss */
.pg-loading-screen .pg-loading-center-outer {
width: 100%;
padding: 0;
display: table !important;
height: 100%;
position: absolute;
top: 0;
left: 0;
margin: 0;
}
/* line 70, ../src/please-wait.scss */
.pg-loading-screen .pg-loading-center-middle {
padding: 0;
vertical-align: middle;
display: table-cell !important;
margin: 0;
text-align: center;
}
/* line 78, ../src/please-wait.scss */
.pg-loading-screen .pg-loading-logo-header, .pg-loading-screen .pg-loading-html {
width: 100%;
opacity: 0;
}
/* line 83, ../src/please-wait.scss */
.pg-loading-screen .pg-loading-logo-header {
text-align: center;
}
/* line 86, ../src/please-wait.scss */
.pg-loading-screen .pg-loading-logo-header img {
display: inline-block !important;
}
/* line 91, ../src/please-wait.scss */
.pg-loading-screen .pg-loading-html {
margin-top: 90px;
}
/* line 94, ../src/please-wait.scss */
.pg-loading-screen .pg-loading-html.pg-loaded {
-webkit-transition: opacity 0.5s cubic-bezier(0.7, 0, 0.3, 1);
-moz-transition: opacity 0.5s cubic-bezier(0.7, 0, 0.3, 1);
-ms-transition: opacity 0.5s cubic-bezier(0.7, 0, 0.3, 1);
-o-transition: opacity 0.5s cubic-bezier(0.7, 0, 0.3, 1);
transition: opacity 0.5s cubic-bezier(0.7, 0, 0.3, 1);
}
/* line 97, ../src/please-wait.scss */
.pg-loading-screen .pg-loading-html.pg-loaded.pg-removing {
opacity: 0;
}
/* line 101, ../src/please-wait.scss */
.pg-loading-screen .pg-loading-html.pg-loaded.pg-loading {
opacity: 1;
}
@-webkit-keyframes pgAnimLoading {
from {
opacity: 0;
}
}
@-moz-keyframes pgAnimLoading {
from {
opacity: 0;
}
}
@-o-keyframes pgAnimLoading {
from {
opacity: 0;
}
}
@-ms-keyframes pgAnimLoading {
from {
opacity: 0;
}
}
@keyframes pgAnimLoading {
from {
opacity: 0;
}
}
@-webkit-keyframes pgAnimLoaded {
from {
opacity: 1;
}
}
@-moz-keyframes pgAnimLoaded {
from {
opacity: 1;
}
}
@-o-keyframes pgAnimLoaded {
from {
opacity: 1;
}
}
@-ms-keyframes pgAnimLoaded {
from {
opacity: 1;
}
}
@keyframes pgAnimLoaded {
from {
opacity: 1;
}
}
/**
* please-wait
* Display a nice loading screen while your app loads
* @author Pathgather <tech@pathgather.com>
* @copyright Pathgather 2015
* @license MIT <http://opensource.org/licenses/mit-license.php>
* @link https://github.com/Pathgather/please-wait
* @module please-wait
* @version 0.0.5
*/
!function(a,b){"object"==typeof exports?b(exports):"function"==typeof define&&define.amd?define(["exports"],b):b(a)}(this,function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q;g=document.createElement("fakeelement"),e=!1,n=!1,d="animationend",m=null,f="Webkit Moz O ms".split(" "),l={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",msTransition:"MSTransitionEnd",transition:"transitionend"};for(h in l)if(o=l[h],null!=g.style[h]){m=o,n=!0;break}if(null!=g.style.animationName&&(e=!0),!e)for(p=0,q=f.length;q>p;p++)if(i=f[p],null!=g.style[""+i+"AnimationName"]){switch(i){case"Webkit":d="webkitAnimationEnd";break;case"Moz":d="animationend";break;case"O":d="oanimationend";break;case"ms":d="MSAnimationEnd"}e=!0;break}return c=function(a,b){return b.classList?b.classList.add(a):b.className+=" "+a},k=function(a,b){return b.classList?b.classList.remove(a):b.className=b.className.replace(a,"").trim()},b=function(){function a(a){var b,f,g,h;b=this.constructor._defaultOptions,this.options={},this.loaded=!1,this.finishing=!1;for(f in b)h=b[f],this.options[f]=null!=a[f]?a[f]:h;this._loadingElem=document.createElement("div"),this._loadingHtmlToDisplay=[],this._loadingElem.className="pg-loading-screen",null!=this.options.backgroundColor&&(this._loadingElem.style.backgroundColor=this.options.backgroundColor),this._loadingElem.innerHTML=this.options.template,this._loadingHtmlElem=this._loadingElem.getElementsByClassName("pg-loading-html")[0],null!=this._loadingHtmlElem&&(this._loadingHtmlElem.innerHTML=this.options.loadingHtml),this._readyToShowLoadingHtml=!1,this._logoElem=this._loadingElem.getElementsByClassName("pg-loading-logo")[0],null!=this._logoElem&&(this._logoElem.src=this.options.logo),k("pg-loaded",document.body),c("pg-loading",document.body),document.body.appendChild(this._loadingElem),c("pg-loading",this._loadingElem),this._onLoadedCallback=this.options.onLoadedCallback,g=function(a){return function(b){return a.loaded=!0,a._readyToShowLoadingHtml=!0,c("pg-loaded",a._loadingHtmlElem),e&&a._loadingHtmlElem.removeEventListener(d,g),a._loadingHtmlToDisplay.length>0&&a._changeLoadingHtml(),a.finishing?(null!=b&&b.stopPropagation(),a._finish()):void 0}}(this),null!=this._loadingHtmlElem&&(e?this._loadingHtmlElem.addEventListener(d,g):g(),this._loadingHtmlListener=function(a){return function(){return a._readyToShowLoadingHtml=!0,k("pg-loading",a._loadingHtmlElem),n&&a._loadingHtmlElem.removeEventListener(m,a._loadingHtmlListener),a._loadingHtmlToDisplay.length>0?a._changeLoadingHtml():void 0}}(this),this._removingHtmlListener=function(a){return function(){return a._loadingHtmlElem.innerHTML=a._loadingHtmlToDisplay.shift(),k("pg-removing",a._loadingHtmlElem),c("pg-loading",a._loadingHtmlElem),n?(a._loadingHtmlElem.removeEventListener(m,a._removingHtmlListener),a._loadingHtmlElem.addEventListener(m,a._loadingHtmlListener)):a._loadingHtmlListener()}}(this))}return a._defaultOptions={backgroundColor:null,logo:null,loadingHtml:null,template:"<div class='pg-loading-inner'>\n <div class='pg-loading-center-outer'>\n <div class='pg-loading-center-middle'>\n <h1 class='pg-loading-logo-header'>\n <img class='pg-loading-logo'></img>\n </h1>\n <div class='pg-loading-html'>\n </div>\n </div>\n </div>\n</div>",onLoadedCallback:null},a.prototype.finish=function(a,b){return null==a&&(a=!1),window.document.hidden&&(a=!0),this.finishing=!0,null!=b&&this.updateOption("onLoadedCallback",b),this.loaded||a?this._finish(a):void 0},a.prototype.updateOption=function(a,b){switch(a){case"backgroundColor":return this._loadingElem.style.backgroundColor=b;case"logo":return this._logoElem.src=b;case"loadingHtml":return this.updateLoadingHtml(b);case"onLoadedCallback":return this._onLoadedCallback=b;default:throw new Error("Unknown option '"+a+"'")}},a.prototype.updateOptions=function(a){var b,c,d;null==a&&(a={}),d=[];for(b in a)c=a[b],d.push(this.updateOption(b,c));return d},a.prototype.updateLoadingHtml=function(a,b){if(null==b&&(b=!1),null==this._loadingHtmlElem)throw new Error("The loading template does not have an element of class 'pg-loading-html'");return b?(this._loadingHtmlToDisplay=[a],this._readyToShowLoadingHtml=!0):this._loadingHtmlToDisplay.push(a),this._readyToShowLoadingHtml?this._changeLoadingHtml():void 0},a.prototype._changeLoadingHtml=function(){return this._readyToShowLoadingHtml=!1,this._loadingHtmlElem.removeEventListener(m,this._loadingHtmlListener),this._loadingHtmlElem.removeEventListener(m,this._removingHtmlListener),k("pg-loading",this._loadingHtmlElem),k("pg-removing",this._loadingHtmlElem),n?(c("pg-removing",this._loadingHtmlElem),this._loadingHtmlElem.addEventListener(m,this._removingHtmlListener)):this._removingHtmlListener()},a.prototype._finish=function(a){var b;return null==a&&(a=!1),null!=this._loadingElem?(c("pg-loaded",document.body),"function"==typeof this._onLoadedCallback&&this._onLoadedCallback.apply(this),b=function(a){return function(){return document.body.removeChild(a._loadingElem),k("pg-loading",document.body),e&&a._loadingElem.removeEventListener(d,b),a._loadingElem=null}}(this),!a&&e?(c("pg-loaded",this._loadingElem),this._loadingElem.addEventListener(d,b)):b()):void 0},a}(),j=function(a){return null==a&&(a={}),new b(a)},a.pleaseWait=j,j});
\ No newline at end of file
此差异已折叠。
{
"name": "Mongoclient",
"main": "index.js",
"electronVersion": "1.6.0",
"version": "2.0.0",
"dependencies": {
"electrify": "2.1.4"
"shelljs": "^0.7.7"
}
}
\ No newline at end of file
- Bundle mongoclient with command: meteor build --server --directory <output> and put contents into app folder
- Copy contents of C:\Users\Sercan\AppData\Local\.meteor\packages\meteor-tool\1.4.3_2\mt-os.windows.x86_32\dev_bundle\server-lib\node_modules to app\programs\server\node_modules
- Copy Node (4.2.7) to bin folder
- Copy Mongod (>=3.2.6) to bin folder
# Steps to RUN directly
- Run npm install -g electron
- Run electron .
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册