diff --git a/package.json b/package.json index cf3355e..4cf6e15 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "BlitzModder", - "version": "1.6.1", + "version": "1.7.0", "description": "Mod manager for World of Tanks Blitz(PC)", "main": "bin/core/core.js", "scripts": { diff --git a/src/core/applyMod.coffee b/src/core/applyMod.coffee index 0277206..3d3cd4a 100644 --- a/src/core/applyMod.coffee +++ b/src/core/applyMod.coffee @@ -49,7 +49,7 @@ _getFromLocal = (folder, mod, log) -> ###* * dataイベントから適応する *### -_applyFromData = (outputFolder, {path: pa, fullPath}, pathList, cb) -> +_applyFromData = (outputFolder, {path: pa, fullPath}, pathList) -> return new Promise( (resolve, reject) -> pathList.add(pa) fstream @@ -69,17 +69,17 @@ _applyFromData = (outputFolder, {path: pa, fullPath}, pathList, cb) -> ###* * entryイベントから適応する *### -_applyFromEntry = (outputFolder, {path: pa}, pathList, cb) -> +_applyFromEntry = (outputFolder, entry, pathList) -> return new Promise( (resolve, reject) -> - pathList.add(pa) + pathList.add(entry.path) entry - .pipe(fstream.Writer(path: path.join(outputFolder, pa))) + .pipe(fstream.Writer(path: path.join(outputFolder, entry.path))) .on("err", (err) -> reject(err) return ) .on("close", -> - resolve(pa) + resolve(entry.path) return ) return @@ -89,15 +89,19 @@ _applyFromEntry = (outputFolder, {path: pa}, pathList, cb) -> * MODを適応します * @param {"add"|"delete"} type 適応するか解除するか * @param {string} mod "{repo: {type: repoType, name: repo}, name: name}" - * @param {Function} callback 適応完了時に実行 + * @param {Function} progress 進捗報告 * @return {Promise} ### -applyMod = (type, mod, callback) -> +applyMod = (type, mod, progress) -> pathType = config.get("blitzPathType") if pathType is "folder" outputFolder = path.normalize(config.get("blitzPath")) else outputFolder = TEMP_FOLDER + + log = (phase) -> + return progress(phase, type, mod) + try pathList = new Set() await fs.ensureDir(outputFolder) @@ -106,13 +110,10 @@ applyMod = (type, mod, callback) -> when "delete" then folder = "remove" else throw new Error("Unknown type") - log = (phase) -> - return callback(phase, type, mod) - if mod.repo.type is "remote" stream = _getFromRemote(folder, mod, log) else if mod.repo.type is "local" - stream = _getFromLocal(folder, mod) + stream = _getFromLocal(folder, mod, log) unless stream? then throw new Error("No Folder and Zip in Path") else throw new Error("Unknown RepoType") @@ -155,8 +156,8 @@ applyMod = (type, mod, callback) -> ) if pathType is "file" - callback("tempdone", type, mod) - callback("zipcompress", type, mod) + log("tempdone") + log("zipcompress") blitzPath = path.normalize(config.get("blitzPath")) switch config.get("platform") when "a" then prefix = "assets" @@ -175,7 +176,7 @@ applyMod = (type, mod, callback) -> .generateNodeStream(streamFiles: true) .pipe(fs.createWriteStream(blitzPath)) .on("finish", -> - callback("zipcompressed", type, mod) + log("zipcompressed") resolve() return ) @@ -187,11 +188,10 @@ applyMod = (type, mod, callback) -> switch type when "add" then config.add("appliedMods", {repo: mod.repo.name, name: mod.name}) when "delete" then config.remove("appliedMods", {repo: mod.repo.name, name: mod.name}) - callback("done", type, mod) - fs.remove(TEMP_FOLDER) + log("done") catch err - fs.remove(TEMP_FOLDER) - callback("fail", type, mod, err) + progress("fail", type, mod, err) + fs.remove(TEMP_FOLDER) return ###* diff --git a/src/core/plistList.coffee b/src/core/plistList.coffee index bee6331..7883fbd 100644 --- a/src/core/plistList.coffee +++ b/src/core/plistList.coffee @@ -125,7 +125,7 @@ filter = (parsedObj, useCache = false) -> if _isNeeded(ver, plat, v3) obj[k1] = {} if !obj[k1]? obj[k1][k2] = {} if !obj[k1][k2]? - obj[k1][k2][k3] = v3.name + obj[k1][k2][k3] = v3 return obj module.exports = { diff --git a/src/core/request.coffee b/src/core/request.coffee index d266517..d3ff0d1 100644 --- a/src/core/request.coffee +++ b/src/core/request.coffee @@ -6,6 +6,7 @@ request = require "request" requestP = require "request-promise-native" path = require "path" fs = require "fs-extra" +util = require "./util" ###* * リモートからファイルを取得します @@ -81,7 +82,13 @@ getLastestVersion = -> * @return {Number} ステータスコード ### getUrlStatus = (url) -> - {statusCode} = await requestP({url, resolveWithFullResponse: true}) + try + {statusCode} = await requestP({url, resolveWithFullResponse: true}) + catch + if util.isFile(url) + return 200 + else + return 404 return statusCode module.exports = { diff --git a/src/gui/css/core.scss b/src/gui/css/core.scss index d561899..7b45830 100644 --- a/src/gui/css/core.scss +++ b/src/gui/css/core.scss @@ -63,6 +63,9 @@ li { .form-control-feedback { flex: 100%; } +#repo .debug-info { + display: none; +} .hidden { display: none; diff --git a/src/gui/debug_repo.haml b/src/gui/debug_repo.haml new file mode 100644 index 0000000..541815b --- /dev/null +++ b/src/gui/debug_repo.haml @@ -0,0 +1,53 @@ +!!! 5 +%html + %head + %meta(charset="utf-8") + %meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no") + %meta(http-equiv="x-ua-compatible" content="ie=edge") + %link(rel="stylesheet" type="text/css" href="../node_modules/bootstrap/dist/css/bootstrap.min.css") + %link(rel="stylesheet" type="text/css" href="css/core.css") + %body + %nav.navbar.navbar-dark.bg-dark.sticky-top.mb-4 + .mr-auto + %a(href="./index.html") + %button.btn(type="button") + %img(src="./img/circle-left.svg" height="30px" width="30px") + %span.navbar-brand.pl-2.translate(data-key="DEBUG_REPOSITORY_NAME") + %button.btn#reload(type="button") + %img(src="./img/loop2.svg" height="30px" width="30px") + %button.btn#apply(type="button") + %img(src="./img/checkmark2.svg" height="30px" width="30px") + .container-fluid#debug-repo + .row#root + %description(:hasinfo="hasinfo" :name="infoname" :version="infoversion" :maintainer="infomaintainer" :changelog="changelog") + .col-12.text-center(v-if="loading") + %img.spin(src="./img/spinner2.svg" height="40px" width="40px") + .col-12(v-else-if="error") + %p + %span.translate(data-key="ERROR_HAPPEN") + %p {{errorMsg}} + %big-category(v-else=true v-for="(v, k) in plist" :name="k" :val="v") + .modal.fade#progress + .modal-dialog + .modal-content + .modal-header + %h4.modal-title + %modal-body(:phase="phase" :log="log" :finished="finished") + .modal-footer + %button.btn.btn-primary(type="button" :disabled="!finished" @click="reset") + %span.translate(data-key="BUTTON_CLOSE") + .modal.fade#detail + .modal-dialog + .modal-content + .modal-body#outwebview + %webview#detailweb(src="about:blank" autosize="on") + .modal-footer + %button.btn.btn-primary(type="button" data-dismiss="modal") + %span.translate(data-key="BUTTON_CLOSE") + %script(type="text/javascript") + window.jQuery = window.$ = require("../node_modules/jquery/dist/jquery.slim.min.js"); + %script(src="../node_modules/popper.js/dist/umd/popper.min.js") + %script(src="../node_modules/bootstrap/dist/js/bootstrap.min.js") + %script(src="../node_modules/vue/dist/vue.min.js") + %script(src="./js/repo_components.js") + %script(src="./js/debug_repo.js") diff --git a/src/gui/index.haml b/src/gui/index.haml index 5a0af0f..d475a76 100644 --- a/src/gui/index.haml +++ b/src/gui/index.haml @@ -14,7 +14,7 @@ %img(src="./img/cogs.svg" height="30px" width="30px") %button.btn#play(type="button") %img(src="./img/play3.svg" height="30px" width="30px") - .container-fluid + .container-fluid#index .row#repo .col-md-6(v-if="remoteRepos.length > 0") .card @@ -32,7 +32,7 @@ .card %h4.card-header %span.translate(data-key="DEBUG_REPOSITORY_NAME") - .card + .card-body %debug-repo(:name="debugRepo") .card.text-white.bg-info#update.hidden .card-body diff --git a/src/gui/js/debug_repo.coffee b/src/gui/js/debug_repo.coffee new file mode 100644 index 0000000..48f668a --- /dev/null +++ b/src/gui/js/debug_repo.coffee @@ -0,0 +1,146 @@ +{remote} = require "electron" +{shell} = remote +plistList = remote.require("./plistList") +plistInfo = remote.require("./plistInfo") +util = remote.require("./util") +config = remote.require("./config") +applyMod = remote.require("./applyMod") +request = remote.require("./request") +lang = remote.require("./lang") + +path = config.get("debugRepo") +repo = + type: "local" + name: path +langName = config.get("lang") + +langTable = lang.get() +transEle = document.getElementsByClassName("translate") +for te in transEle + te.textContent = langTable[te.dataset.key] + +r = new Vue( + el: "#root" + data: + loading: false + error: false + errorMsg: "" + plist: {} + hasinfo: false + infoname: "" + infoversion: "" + infomaintainer: "" + changelog: "" + created: -> + do => + await @getPlist() + await r.getPlistWithOutBlackout(true) + return + @getInfo() + @getChangelog() + return + methods: + getPlist: (force = false) -> + @loading = true + @error = false + try + obj = await plistList.getUntilDone(repo, langName, force) + @loading = false + @plist = obj + catch err + @loading = false + @error = true + @errorMsg = err + return + getPlistWithOutBlackout: (force = false) -> + @error = false + try + obj = await plistList.getUntilDone(repo, langName, force) + if JSON.stringify(@plist) isnt JSON.stringify(obj) + @plist = obj + catch err + @error = true + @errorMsg = err + return + getInfo: (force = false) -> + try + {name, version, maintainer} = await plistInfo.get(repo, force) + @hasinfo = true + @infoname = name + @infoversion = version + @infomaintainer = maintainer + catch + @hasinfo = false + return + getChangelog: -> + @changelog = await request.getChangelog(repo) + return +) + +onBeforeClose = (e) -> + if !confirm(langTable.CONFIRM_APPLY_CLOSE_STRING) + e.returnValue = false + return + +document.getElementById("apply").addEventListener("click", -> + return unless confirm(langTable.CONFIRM_APPLY_STRING) + # 閉じる防止 + window.addEventListener("beforeunload", onBeforeClose) + + addMods = [] + deleteMods = [] + for $mod in $("button:not(.applied) input:checked") + addMods.push({repo, name: $mod.dataset.path, showname: $mod.dataset.name}) + for $mod in $("button.applied input:not(:checked)") + deleteMods.push({repo, name: $mod.dataset.path, showname: $mod.dataset.name}) + + $("#progress").modal({ keyboard: false, focus: true, backdrop: "static" }) + errored = false + await applyMod.applyMods(addMods, deleteMods, (phase, type, mod, err) -> + if phase is "done" + $button = $("button[data-path=\"#{mod.name}\"]") + switch type + when "add" + $button.addClass("applied") + p.addLog(mod.showname, langTable.MODAL_LOG_APPLIED) + when "delete" + $button.removeClass("applied") + p.addLog(mod.showname, langTable.MODAL_LOG_REMOVED) + else if phase is "fail" + $checkbox = $("button[data-path=\"#{mod.name}\"]").find("input") + errored = true + switch type + when "add" + p.addLog(mod.showname, langTable.MODAL_LOG_FAILED_APPLY+"(#{err})") + $checkbox.prop("checked", false) + when "delete" + p.addLog(mod.showname, langTable.MODAL_LOG_FAILED_REMOVE+"(#{err})") + $checkbox.prop("checked", true) + else + switch phase + when "download" + p.addLog(mod.showname, langTable.MODAL_LOG_DOWNLOAD_START) + when "downloaded" + p.addLog(mod.showname, langTable.MODAL_LOG_DOWNLOAD_FINISH) + when "copydir" + p.addLog(mod.showname, langTable.MODAL_LOG_COPY_START) + when "zipextract" + p.addLog(mod.showname, langTable.MODAL_LOG_EXTRACT_START) + when "zipextracted" + p.addLog(mod.showname, langTable.MODAL_LOG_EXTRACT_FINISH) + when "tempdone" + p.addLog(mod.showname, langTable.MODAL_LOG_TEMP_DONE) + when "zipcompress" + p.addLog(mod.showname, langTable.MODAL_LOG_COMPRESS_START) + when "zipcompressed" + p.addLog(mod.showname, langTable.MODAL_LOG_COMPRESS_FINISH) + return + ) + if !errored + p.changePhase("done") + else + p.changePhase("failed") + # 閉じる防止解除 + window.removeEventListener("beforeunload", onBeforeClose) + return +) diff --git a/src/gui/js/repo.coffee b/src/gui/js/repo.coffee index e68aa67..1b9658b 100644 --- a/src/gui/js/repo.coffee +++ b/src/gui/js/repo.coffee @@ -20,116 +20,6 @@ transEle = document.getElementsByClassName("translate") for te in transEle te.textContent = langTable[te.dataset.key] -appliedMods = -> - return config.get("appliedMods") - -Vue.component("description", - template: """ -
-
-
-

{{name}}

-
{{version}}
-

#{langTable.REPO_MAINTAINER}: {{maintainer}}

- -
-
-
-
-
-
- """ - props: ["hasinfo", "name", "version", "maintainer", "changelog"] - computed: - hasChangelog: -> - return (@changelog isnt "") - changelogHtml: -> - return @changelog.replace(/\n/g, "
") -) -Vue.component("big-category", - template: """ -
-
-

{{name}}

- -
-
- """ - props: ["name", "val"] -) -Vue.component("small-category", - template: """ -
  • - {{name}} -
    -
    - -
    -
    -
  • - """ - props: ["parentname", "name", "val"] - computed: - id: -> - return "#"+@idName - idName: -> - return "category-"+btoa(unescape(encodeURIComponent(@parentname+@name))).replace(/=|\+|\//g, "") -) -firstExec = true -Vue.component("mod", - template: """ - - """ - props: ["name", "val"] - data: -> - applied = (do => - for mod in appliedMods() when mod.repo is path - if mod.name is @val - return true - return false - ) - return { - checked: applied - applied - } - computed: - link: -> - return request.getDetailUrl(repo, @val) - methods: - show: ({target}) -> - t = target.classList - return if t.contains("form-check-label") - return if t.contains("form-check-input") - - detail = $("#detail") - webview = detail.find("webview")[0] - code = await request.getUrlStatus(@link) - return if code is 404 - if firstExec - webview.addEventListener("dom-ready", ready = => - webview.removeEventListener("dom-ready", ready) - firstExec = false - webview.loadURL(@link) - return - ) - else - detail.on("shown.bs.modal", ready = => - detail.off("shown.bs.modal", ready) - webview.loadURL(@link) - return - ) - detail.modal("show") - return -) r = new Vue( el: "#root" data: @@ -190,70 +80,6 @@ r = new Vue( return ) - - -Vue.component("modal-body", - template: """ - - """ - props: ["phase", "log", "finished"] - computed: - message: -> - switch @phase - when "standby", "doing" - return langTable.MODAL_TITLE_APPLYING - when "done" - return langTable.MODAL_TITLE_APPLIED - when "failed" - return langTable.MODAL_TITLE_FAILED -) -p = new Vue( - el: "#progress" - data: - phase: "standby" #"standby"|"done"|"failed" - log: "" - computed: - finished: -> - return @phase is "done" or @phase is "failed" - methods: - addLog: (m, d) -> - if @log is "" - @log = "#{util.escape(m)} - #{util.escape(d)}" - else - @log += "
    #{util.escape(m)} - #{util.escape(d)}" - return - nextLog: -> - @log += "
    " - return - deleteLog: -> - @log = "" - return - changePhase: (s) -> - @phase = s - return - reset: -> - $("#progress").modal("hide") - @phase = "standby" - @log = "" - return -) - -document.getElementById("reload").addEventListener("click", -> - r.getPlist(true) - r.getInfo(true) - r.getChangelog() - return -) - onBeforeClose = (e) -> if !confirm(langTable.CONFIRM_APPLY_CLOSE_STRING) e.returnValue = false @@ -321,12 +147,3 @@ document.getElementById("apply").addEventListener("click", -> window.removeEventListener("beforeunload", onBeforeClose) return ) - -webview = document.getElementById("detailweb") -webview.addEventListener("new-window", ({url}) -> - shell.openExternal(url) -) -webview.addEventListener("will-navigate", ({url}) -> - shell.openExternal(url) - webview.stop() -) diff --git a/src/gui/js/repo_components.coffee b/src/gui/js/repo_components.coffee new file mode 100644 index 0000000..7d12a25 --- /dev/null +++ b/src/gui/js/repo_components.coffee @@ -0,0 +1,193 @@ +{remote} = require "electron" +config = remote.require("./config") +request = remote.require("./request") +lang = remote.require("./lang") + +isDebugRepo = location.pathname.includes("debug_repo.html") +langTable = lang.get() +appliedMods = -> + return config.get("appliedMods") + +Vue.component("description", + template: """ +
    +
    +
    +

    {{name}}

    +
    {{version}}
    +

    #{langTable.REPO_MAINTAINER}: {{maintainer}}

    + +
    +
    +
    +
    +
    +
    + """ + props: ["hasinfo", "name", "version", "maintainer", "changelog"] + computed: + hasChangelog: -> + return (@changelog isnt "") + changelogHtml: -> + return @changelog.replace(/\n/g, "
    ") +) +Vue.component("big-category", + template: """ +
    +
    +

    {{name}}

    + +
    +
    + """ + props: ["name", "val"] +) +Vue.component("small-category", + template: """ +
  • + {{name}} +
    +
    + +
    +
    +
  • + """ + props: ["parentname", "name", "val"] + computed: + id: -> + return "#"+@idName + idName: -> + return "category-"+btoa(unescape(encodeURIComponent(@parentname+@name))).replace(/=|\+|\//g, "") +) + +firstExec = true +Vue.component("mod", + template: """ + + """ + props: ["name", "val"] + data: -> + applied = (do => + for mod in appliedMods() when mod.repo is path + if mod.name is @val.name + return true + return false + ) + return { + checked: applied + applied + } + computed: + link: -> + return request.getDetailUrl(repo, @val.name) + methods: + show: ({target}) -> + t = target.classList + return if t.contains("form-check-label") + return if t.contains("form-check-input") + + detail = $("#detail") + webview = detail.find("webview")[0] + code = await request.getUrlStatus(@link) + return if code is 404 + if firstExec + webview.addEventListener("dom-ready", ready = => + webview.removeEventListener("dom-ready", ready) + firstExec = false + webview.loadURL(@link) + return + ) + else + detail.on("shown.bs.modal", ready = => + detail.off("shown.bs.modal", ready) + webview.loadURL(@link) + return + ) + detail.modal("show") + return +) + + +Vue.component("modal-body", + template: """ + + """ + props: ["phase", "log", "finished"] + computed: + message: -> + switch @phase + when "standby", "doing" + return langTable.MODAL_TITLE_APPLYING + when "done" + return langTable.MODAL_TITLE_APPLIED + when "failed" + return langTable.MODAL_TITLE_FAILED +) + +p = new Vue( + el: "#progress" + data: + phase: "standby" #"standby"|"done"|"failed" + log: "" + computed: + finished: -> + return @phase is "done" or @phase is "failed" + methods: + addLog: (m, d) -> + if @log is "" + @log = "#{util.escape(m)} - #{util.escape(d)}" + else + @log += "
    #{util.escape(m)} - #{util.escape(d)}" + return + nextLog: -> + @log += "
    " + return + deleteLog: -> + @log = "" + return + changePhase: (s) -> + @phase = s + return + reset: -> + $("#progress").modal("hide") + @phase = "standby" + @log = "" + return +) + +document.getElementById("reload").addEventListener("click", -> + r.getPlist(true) + r.getInfo(true) + r.getChangelog() + return +) + +webview = document.getElementById("detailweb") +webview.addEventListener("new-window", ({url}) -> + shell.openExternal(url) +) +webview.addEventListener("will-navigate", ({url}) -> + shell.openExternal(url) + webview.stop() +) diff --git a/src/gui/repo.haml b/src/gui/repo.haml index e56d4c2..dda83dc 100644 --- a/src/gui/repo.haml +++ b/src/gui/repo.haml @@ -16,7 +16,7 @@ %img(src="./img/loop2.svg" height="30px" width="30px") %button.btn#apply(type="button") %img(src="./img/checkmark2.svg" height="30px" width="30px") - .container-fluid + .container-fluid#repo .row#root %description(:hasinfo="hasinfo" :name="infoname" :version="infoversion" :maintainer="infomaintainer" :changelog="changelog") .col-12.text-center(v-if="loading") @@ -48,4 +48,5 @@ %script(src="../node_modules/popper.js/dist/umd/popper.min.js") %script(src="../node_modules/bootstrap/dist/js/bootstrap.min.js") %script(src="../node_modules/vue/dist/vue.min.js") + %script(src="./js/repo_components.js") %script(src="./js/repo.js") diff --git a/src/package.json b/src/package.json index 05b4a18..69feea6 100644 --- a/src/package.json +++ b/src/package.json @@ -1,6 +1,6 @@ { "name": "BlitzModder", - "version": "1.6.1", + "version": "1.7.0", "description": "Mod manager for World of Tanks Blitz(PC)", "main": "core/core.js", "author": "S(FV293b)",