Skip to content

Commit cd6bf9e

Browse files
authored
Merge pull request #1 from xuanzhi33:feat-versionList
feat: game Install & version management
2 parents ba19b67 + 21f3a17 commit cd6bf9e

File tree

3 files changed

+252
-6
lines changed

3 files changed

+252
-6
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Still under development / 仍在开发中,功能不完善
1313
- [ ] Game version management / 游戏版本管理
1414
- [ ] Mod management / 模组管理
1515
- [ ] Cross-platform / 跨平台
16+
- [ ] Java version management / Java 版本管理
1617
- [ ] Auto-update / 自动更新
1718

1819
Based on [CMCL](https://github.com/MrShieh-X/console-minecraft-launcher)

smcl.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,13 @@ def _set_window(self, window: Window):
4545
def run_js(self, js):
4646
return self._window.evaluate_js(js)
4747
def cmd_result(self, result):
48-
self.run_js(f"window.cmdResult(`{result}`)")
48+
resultJson = json.dumps(result)
49+
self.run_js(f"window.cmdResult({resultJson});")
4950
def cmcl_waiting(self, cmd):
5051
args = [CMCL]
5152
if cmd is not None:
5253
self.log(f"CMCL: {cmd}")
53-
args += cmd.split(" ")
54+
args += cmd
5455
else:
5556
self.log(f"CMCL: Launch Game")
5657

src/main.html

+248-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<link href="./css/bootstrap.min.css" rel="stylesheet">
1414
<link href="./css/font-awesome.min.css" rel="stylesheet">
1515
<link rel="stylesheet" href="./css/style.css">
16-
<link ref="icon" href="./image/logo.png">
16+
<link rel="icon" href="./image/logo.png">
1717
</head>
1818

1919
<body>
@@ -31,6 +31,10 @@
3131
<player-list v-else-if="ui === 'playerList'" :cur="curPlayerIndex" @close="changeUI('main')" :init="init"
3232
:players="players" :aoa="addOfflineAccount"
3333
:account="account"></player-list>
34+
<version-list v-else-if="ui === 'versionList'" :cur-ver="curVer"
35+
@install="installVersion"
36+
@close="changeUI('main')"
37+
:init="init"></version-list>
3438
<text-input v-else-if="ui === 'input'" @finish="input.resolveFunc" @close="goBack" :title="input.title"
3539
:desc="input.desc" :placeholder="input.placeholder"></text-input>
3640
<div v-else-if="ui === 'main'">
@@ -59,7 +63,7 @@
5963

6064
<transition>
6165
<div class="main-menu" v-if="ui === 'main'">
62-
<button class="ctrl button">
66+
<button class="ctrl button" @click="changeUI('versionList')">
6367
<i class="fa fa-bars"></i>
6468
{{ $t('versionList') }}
6569
</button> &nbsp;
@@ -256,6 +260,89 @@
256260
</template>
257261
</ui-container>
258262
</template>
263+
264+
265+
<!-- TODO: Version List template -->
266+
<template id="version-list">
267+
<transition mode="out-in">
268+
<ui-container v-if="mode === 'list'">
269+
<template #title>
270+
<i class="fa fa-list"></i>
271+
{{ $t('versionList') }}
272+
</template>
273+
<template #content>
274+
<div class="mx-3">
275+
<transition mode="out-in">
276+
<div v-if="loading.version" class="text-muted">
277+
<span class="spinner-border spinner-border-sm"></span>
278+
{{ $t('loading') }}
279+
</div>
280+
<div v-else-if="versions.length === 0" class="text-muted">
281+
<i class="fa fa-minus-circle"></i>
282+
{{ $t('noVersions') }}
283+
</div>
284+
<div v-else>
285+
<transition-group name="list">
286+
<div v-for="ver in versions" class="clearfix mt-2" :key="ver">
287+
<div class="float-left">
288+
<input type="radio" name="version" :value="ver" v-model="selectedVersion" :id="'ver-' + ver"> <label
289+
:for="'ver-' + ver">
290+
{{ ver }}
291+
</div>
292+
<div class="float-right">
293+
<button class="btn btn-sm btn-danger" @click="deleteVersion(ver)">
294+
<i class="fa fa-trash"></i>
295+
</button>
296+
</div>
297+
</div>
298+
</transition-group>
299+
</div>
300+
</transition>
301+
302+
</div>
303+
<button class="btn btn-info btn-block mt-2" @click="mode = 'install'">
304+
<i class="fa fa-download"></i>
305+
{{ $t('install') }}
306+
</button>
307+
<button class="btn btn-success btn-block mt-2" @click="$emit('close')">
308+
<i class="fa fa-check"></i> {{ $t('finish') }}
309+
</button>
310+
</template>
311+
</ui-container>
312+
<ui-container v-else-if="mode === 'install'">
313+
<template #title>
314+
<i class="fa fa-download"></i>
315+
{{ $t('versionSelect') }}
316+
</template>
317+
<template #content>
318+
<button class="btn btn-secondary mb-2" @click="mode = 'list'">
319+
<i class="fa fa-times"></i>
320+
{{ $t('cancel') }}
321+
</button>&nbsp;
322+
<select class="btn btn-info mb-2" v-model="versionType">
323+
<option v-for="item in types" :value="item">{{ $t(item) }}</option>
324+
</select>
325+
<transition mode="out-in">
326+
<div v-if="loading.allVersions" class="text-muted">
327+
<span class="spinner-border spinner-border-sm"></span>
328+
{{ $t('loadingAll') }}
329+
</div>
330+
<div v-else class="mx-3">
331+
<template v-for="ver in allVersions">
332+
<button class="btn btn-success mt-2" @click="$emit('install', ver)">
333+
<i class="fa fa-download"></i>
334+
{{ ver }}
335+
</button>&nbsp;
336+
</template>
337+
</div>
338+
</transition>
339+
</template>
340+
</ui-container>
341+
342+
</transition>
343+
</template>
344+
345+
259346
<!-- TODO: Component templates -->
260347

261348
<script src="./vue.global.prod.js"></script>
@@ -293,7 +380,10 @@
293380
addOfflineAccountFailed: "Failed to add offline account",
294381
nameInvalid: "The name you entered is invalid, please try again",
295382
accountNamePlaceholder: "Enter account name",
296-
383+
install: "Install Game",
384+
installing: "Installing {ver}...",
385+
downloadingResource: "Downloading resources...",
386+
versionNotFound: "Version not found, please reinstall"
297387
// TODO: i18n en
298388
},
299389
zh: {
@@ -318,7 +408,11 @@
318408
accountNameTips: "请输入账号名称,只能包含字母和数字,长度为 3-16 位",
319409
accountNamePlaceholder: "输入账号名称",
320410
addOfflineAccountFailed: "添加离线账户失败",
321-
nameInvalid: "您输入的名称不符合要求,请重新添加"
411+
nameInvalid: "您输入的名称不符合要求,请重新添加",
412+
install: "安装游戏",
413+
installing: "正在安装 {ver}...",
414+
downloadingResource: "正在下载资源文件...",
415+
versionNotFound: "未找到此版本,请重新安装"
322416
// TODO: i18n zh
323417
}
324418
}
@@ -564,13 +658,139 @@
564658
}
565659
};
566660

661+
// TODO: VersionList
662+
const VersionList = {
663+
emits: ["close", "install"],
664+
props: {
665+
curVer: {
666+
type: String
667+
},
668+
init: {
669+
type: Function
670+
}
671+
},
672+
components: {
673+
UiContainer
674+
},
675+
template: "#version-list",
676+
data() {
677+
return {
678+
versions: [],
679+
loading: {
680+
version: true,
681+
allVersions: true
682+
},
683+
allVersions: [],
684+
mode: "list",
685+
versionType: "release",
686+
types: [
687+
"release",
688+
"all",
689+
"snapshot",
690+
"oldAlpha",
691+
"oldBeta"
692+
],
693+
selectedVersion: this.curVer
694+
};
695+
},
696+
watch: {
697+
async mode(val) {
698+
if (val === "list") {
699+
await this.getVersions();
700+
} else {
701+
await this.getAllVersions();
702+
}
703+
},
704+
async versionType(val) {
705+
await this.getAllVersions();
706+
},
707+
async selectedVersion(val) {
708+
if (val) {
709+
await api.cmcl(["--select", val]);
710+
} else {
711+
await api.cmcl(["config", "--delete", "selectedVersion"]);
712+
}
713+
714+
await this.init();
715+
}
716+
},
717+
methods: {
718+
async selectVersion(ver) {
719+
await api.cmcl(["--select", ver]);
720+
await this.init();
721+
},
722+
async getAllVersions() {
723+
this.loading.allVersions = true;
724+
const allVersionsStr = await api.cmcl(["install", "--show", this.versionType]);
725+
this.allVersions = allVersionsStr.split(/\s+/).slice(0, -1);
726+
this.allVersions.reverse();
727+
this.loading.allVersions = false;
728+
},
729+
async getVersions() {
730+
this.loading.version = true;
731+
const versionStr = await api.cmcl(["--list"]);
732+
this.versions = versionStr.split("\n").slice(1).join("\n").split(/\s+/).slice(0, -1);
733+
if (this.versions.length === 1 && this.versions[0] === "") {
734+
this.versions = [];
735+
}
736+
737+
this.loading.version = false;
738+
},
739+
async deleteVersion(ver) {
740+
if (await api.confirm_dialog(this.$t("deleteConfirm"), this.$t("deleteVersion"))) {
741+
await api.cmcl(["version", ver, "--delete"]);
742+
if (this.selectedVersion === ver) {
743+
this.selectedVersion = "";
744+
}
745+
await this.getVersions();
746+
}
747+
}
748+
},
749+
async mounted() {
750+
await this.getVersions();
751+
},
752+
i18n: {
753+
messages: {
754+
en: {
755+
loading: "Getting installed versions...",
756+
loadingAll: "Getting all versions...",
757+
noVersions: "No versions installed, please click the button below to install",
758+
install: "Install a game version",
759+
versionSelect: "Select a game version",
760+
release: "Release",
761+
all: "All Versions",
762+
snapshot: "Snapshot",
763+
oldAlpha: "Old Alpha",
764+
oldBeta: "Old Beta",
765+
deleteConfirm: "Are you sure to delete this version?",
766+
deleteVersion: "Delete Version"
767+
},
768+
zh: {
769+
loading: "正在获取已安装的版本...",
770+
loadingAll: "正在获取可安装的版本...",
771+
noVersions: "当前未安装任何版本,请点击下方按钮安装",
772+
install: "安装一个游戏版本",
773+
versionSelect: "选择要安装的游戏版本",
774+
release: "正式版",
775+
all: "所有版本",
776+
snapshot: "快照版",
777+
oldAlpha: "远古 Alpha 版",
778+
oldBeta: "远古 Beta 版",
779+
deleteConfirm: "确定要删除这个版本吗?",
780+
deleteVersion: "删除版本"
781+
}
782+
}
783+
}
784+
};
785+
567786
// TODO: Components
568787
const app = createApp({
569788
components: {
570789
Loading,
571790
Warning,
572791
Settings,
573792
PlayerList,
793+
VersionList,
574794
TextInput
575795
},
576796
data() {
@@ -612,6 +832,26 @@
612832
await this.init();
613833
return res;
614834
},
835+
836+
// TODO: installVersion
837+
async installVersion(ver) {
838+
this.startLoading(this.$t("install"), this.$t("installing", { ver }), (result, loading) => {
839+
const msg = result.replace(/\u0008/g, "").replace(/\(\d+%\)/g, "");
840+
console.log(msg);
841+
const reg = /\[(\d+)\/(\d+)\]/;
842+
if (reg.test(msg)) {
843+
const match = reg.exec(msg);
844+
loading.progress = parseInt(match[1]) / parseInt(match[2]) * 100;
845+
loading.log = this.$t("downloadingResource") + ` (${match[1]}/${match[2]})`;
846+
} else {
847+
loading.log = msg;
848+
loading.progress += 1;
849+
}
850+
});
851+
const res = await api.cmcl_waiting(["install", ver]);
852+
this.finishLoading();
853+
},
854+
615855
async addOfflineAccount() {
616856
const name = await this.requestInput(
617857
this.$t("addOfflineAccount"),
@@ -671,6 +911,7 @@
671911
async launchGame() {
672912
// TODO: launch game
673913
if (!this.curVer) {
914+
this.changeUI("versionList");
674915
return;
675916
}
676917

@@ -703,6 +944,9 @@
703944
const reg = /^(|Game crash possible error: ).*$/m;
704945
const match = reg.exec(res);
705946
this.showWarning(this.$t("launchFail"), match[0]);
947+
} else if (res.includes("install <")) {
948+
this.finishLoading();
949+
this.showWarning(this.$t("launchFail"), this.$t("versionNotFound"));
706950
}
707951
},
708952
async init() {

0 commit comments

Comments
 (0)