Create settings page and image cache (#27)
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
29ab978612
commit
a73828fc86
24 changed files with 556 additions and 70 deletions
|
@ -2,7 +2,7 @@ module.exports = {
|
||||||
moduleFileExtensions: ["js", "jsx", "json", "vue", "ts", "tsx"],
|
moduleFileExtensions: ["js", "jsx", "json", "vue", "ts", "tsx"],
|
||||||
transform: {
|
transform: {
|
||||||
"^.+\\.vue$": "vue-jest",
|
"^.+\\.vue$": "vue-jest",
|
||||||
".+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$":
|
".+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2|webp)$":
|
||||||
"jest-transform-stub",
|
"jest-transform-stub",
|
||||||
"^.+\\.tsx?$": "ts-jest"
|
"^.+\\.tsx?$": "ts-jest"
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"test:unit": "vue-cli-service test:unit"
|
"test:unit": "vue-cli-service test:unit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/jszip": "^3.1.6",
|
||||||
"axios": "^0.18.0",
|
"axios": "^0.18.0",
|
||||||
"babel-core": "7.0.0-bridge.0",
|
"babel-core": "7.0.0-bridge.0",
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.1",
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
"core-js": "^2.6.5",
|
"core-js": "^2.6.5",
|
||||||
"dexie": "^2.0.4",
|
"dexie": "^2.0.4",
|
||||||
"eventemitter3": "^4.0.0",
|
"eventemitter3": "^4.0.0",
|
||||||
|
"jszip": "^3.2.2",
|
||||||
"node-sass": "^4.9.0",
|
"node-sass": "^4.9.0",
|
||||||
"peerjs": "^1.0.4",
|
"peerjs": "^1.0.4",
|
||||||
"register-service-worker": "^1.6.2",
|
"register-service-worker": "^1.6.2",
|
||||||
|
@ -28,7 +30,8 @@
|
||||||
"vue-property-decorator": "^8.1.0",
|
"vue-property-decorator": "^8.1.0",
|
||||||
"vue-router": "^3.0.3",
|
"vue-router": "^3.0.3",
|
||||||
"vuex": "^3.0.1",
|
"vuex": "^3.0.1",
|
||||||
"vuex-class": "^0.3.2"
|
"vuex-class": "^0.3.2",
|
||||||
|
"worker-loader": "^2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^23.1.4",
|
"@types/jest": "^23.1.4",
|
||||||
|
|
|
@ -28,9 +28,8 @@ h1.loading-message {
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-property-decorator";
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
import { Action, Getter } from "vuex-class";
|
import { Action, Getter } from "vuex-class";
|
||||||
import { loadSets } from "@/mlpccg/set";
|
import { AppState } from "@/store/types";
|
||||||
import { AppState } from "./store/types";
|
import { refreshCardSource, loadSets, getCards } from "@/mlpccg";
|
||||||
import { getCards } from "./mlpccg/database";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {}
|
components: {}
|
||||||
|
@ -54,6 +53,7 @@ export default class App extends Vue {
|
||||||
private async loadCards() {
|
private async loadCards() {
|
||||||
this.showLoading("Downloading data for all sets");
|
this.showLoading("Downloading data for all sets");
|
||||||
await loadSets();
|
await loadSets();
|
||||||
|
await refreshCardSource();
|
||||||
this.hideLoading();
|
this.hideLoading();
|
||||||
this.setLoaded(true);
|
this.setLoaded(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,3 +16,26 @@ html {
|
||||||
body {
|
body {
|
||||||
color: $white;
|
color: $white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-background {
|
||||||
|
background: rgba($black, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
border: 1px solid rgba($primary, 0.4);
|
||||||
|
box-shadow: 0 0 30px rgba($black, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-card-body {
|
||||||
|
background: rgba($black, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-card-head,
|
||||||
|
.modal-card-foot {
|
||||||
|
background: rgba($black, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
progress.progress {
|
||||||
|
filter: invert(90%);
|
||||||
|
}
|
50
src/components/Cards/CardImage.vue
Normal file
50
src/components/Cards/CardImage.vue
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<template>
|
||||||
|
<img :src="imageURL" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Vue, Prop } from "vue-property-decorator";
|
||||||
|
import { cardImageURL } from "../../mlpccg";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {}
|
||||||
|
})
|
||||||
|
export default class CardImage extends Vue {
|
||||||
|
@Prop()
|
||||||
|
private id!: string;
|
||||||
|
|
||||||
|
private loaded!: boolean;
|
||||||
|
private loadedURL!: string;
|
||||||
|
|
||||||
|
private data() {
|
||||||
|
return {
|
||||||
|
loaded: false,
|
||||||
|
loadedURL: ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private mounted() {
|
||||||
|
this.fetchImage();
|
||||||
|
this.$watch("id", () => {
|
||||||
|
this.loaded = false;
|
||||||
|
this.fetchImage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchImage() {
|
||||||
|
const url = await cardImageURL(this.id);
|
||||||
|
this.loaded = true;
|
||||||
|
this.loadedURL = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get imageURL(): string {
|
||||||
|
if (this.loaded) {
|
||||||
|
return this.loadedURL;
|
||||||
|
}
|
||||||
|
return require("@/assets/images/cardback.webp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -6,7 +6,7 @@
|
||||||
v-for="(card, i) in cards"
|
v-for="(card, i) in cards"
|
||||||
:key="i + card.data.ID"
|
:key="i + card.data.ID"
|
||||||
>
|
>
|
||||||
<img :src="imageURL(card.data.ID)" />
|
<CardImage :id="card.data.ID" />
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
@ -45,9 +45,12 @@ $padding: 10px;
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue, Prop } from "vue-property-decorator";
|
import { Component, Vue, Prop } from "vue-property-decorator";
|
||||||
import { Card, CardSlot, cardImageURL } from "@/mlpccg";
|
import { Card, CardSlot, cardImageURL } from "@/mlpccg";
|
||||||
|
import CardImage from "@/components/Cards/CardImage.vue";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {}
|
components: {
|
||||||
|
CardImage
|
||||||
|
}
|
||||||
})
|
})
|
||||||
export default class CardPicker extends Vue {
|
export default class CardPicker extends Vue {
|
||||||
@Prop()
|
@Prop()
|
||||||
|
|
|
@ -1,18 +1,36 @@
|
||||||
<template>
|
<template>
|
||||||
<nav>
|
<nav>
|
||||||
<router-link
|
<section class="pages">
|
||||||
:class="routeClass(route)"
|
<router-link
|
||||||
v-for="route in routes"
|
:class="routeClass(route)"
|
||||||
:key="route"
|
v-for="route in mainRoutes"
|
||||||
:to="{ name: route }"
|
:key="route"
|
||||||
>{{ prettyTitle(route) }}</router-link
|
:to="{ name: route }"
|
||||||
>
|
>{{ prettyTitle(route) }}</router-link
|
||||||
|
>
|
||||||
|
</section>
|
||||||
|
<section class="icons">
|
||||||
|
<router-link
|
||||||
|
:class="routeClass(route)"
|
||||||
|
v-for="route in iconRoutes"
|
||||||
|
:key="route"
|
||||||
|
:to="{ name: route }"
|
||||||
|
><b-icon
|
||||||
|
:icon="prettyTitle(route)"
|
||||||
|
class="route-icon"
|
||||||
|
custom-size="mdi-36px"
|
||||||
|
/></router-link>
|
||||||
|
</section>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "@/assets/scss/_variables.scss";
|
@import "@/assets/scss/_variables.scss";
|
||||||
|
|
||||||
|
.route-icon {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -22,13 +40,23 @@ nav {
|
||||||
rgba(100, 180, 255, 0.1)
|
rgba(100, 180, 255, 0.1)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
.pages {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icons {
|
||||||
|
flex-grow: 0;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.entry {
|
.entry {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
max-width: 300px;
|
max-width: 250px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: $grey-lighter;
|
color: $grey-lighter;
|
||||||
|
|
||||||
|
@ -61,17 +89,20 @@ nav {
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-property-decorator";
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
|
|
||||||
const routes = ["lobby", "deck-editor"];
|
const mainRoutes = ["lobby", "deck-editor"];
|
||||||
|
const iconRoutes = ["settings"];
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {}
|
components: {}
|
||||||
})
|
})
|
||||||
export default class TopNav extends Vue {
|
export default class TopNav extends Vue {
|
||||||
private routes!: string[];
|
private mainRoutes!: string[];
|
||||||
|
private iconRoutes!: string[];
|
||||||
|
|
||||||
private data() {
|
private data() {
|
||||||
return {
|
return {
|
||||||
routes
|
mainRoutes,
|
||||||
|
iconRoutes
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,18 @@ class CardDatabase extends Dexie {
|
||||||
|
|
||||||
export let Database: CardDatabase | null = null;
|
export let Database: CardDatabase | null = null;
|
||||||
|
|
||||||
export function initDB() {
|
export async function initDB(): Promise<void> {
|
||||||
if (Database == null) {
|
return new Promise(resolve => {
|
||||||
Database = new CardDatabase();
|
if (Database == null) {
|
||||||
}
|
Database = new CardDatabase();
|
||||||
|
Database.on("ready", async () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
Database.open();
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCards(filter: CardFilter) {
|
export async function getCards(filter: CardFilter) {
|
||||||
|
|
|
@ -1,35 +1,37 @@
|
||||||
import axios from "axios";
|
|
||||||
import { Database } from "./database";
|
import { Database } from "./database";
|
||||||
|
|
||||||
const imgBaseURL = "https://mcg.zyg.ovh/images/cards/";
|
const imgBaseURL = "https://mcg.zyg.ovh/images/cards/";
|
||||||
|
let imageSource: "local" | "remote" = "remote";
|
||||||
|
|
||||||
export function cardImageURL(cardid: string): string {
|
export function remoteImageURL(cardid: string): string {
|
||||||
return `${imgBaseURL}${cardid}.webp`;
|
return `${imgBaseURL}${cardid}.webp`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getCardImageList(): Promise<string[]> {
|
export async function cardImageURL(cardid: string): Promise<string> {
|
||||||
const req = await axios(`${imgBaseURL}list.txt`);
|
if (!Database) {
|
||||||
return req.data;
|
return remoteImageURL(cardid);
|
||||||
|
}
|
||||||
|
switch (cardImageSource()) {
|
||||||
|
case "local":
|
||||||
|
const card = await Database.images.get(`${cardid}.webp`);
|
||||||
|
if (!card) {
|
||||||
|
return remoteImageURL(cardid);
|
||||||
|
}
|
||||||
|
return URL.createObjectURL(card.image);
|
||||||
|
//TODO
|
||||||
|
case "remote":
|
||||||
|
return remoteImageURL(cardid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getImages() {
|
export function cardImageSource() {
|
||||||
if (Database == null) {
|
return imageSource;
|
||||||
throw new Error("Database was not initialized, init with 'initDB()'");
|
}
|
||||||
}
|
|
||||||
const itemcount = await Database.images.count();
|
export async function refreshCardSource() {
|
||||||
if (itemcount > 100) {
|
if (!Database) {
|
||||||
// DB already filled, exit early
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const imglist = await getCardImageList();
|
const count = await Database.images.count();
|
||||||
|
imageSource = count > 1900 ? "local" : "remote";
|
||||||
let table = Database.images;
|
|
||||||
const promises = imglist.map(async img => {
|
|
||||||
const req = await axios({
|
|
||||||
url: `${imgBaseURL}${img}`,
|
|
||||||
responseType: "blob"
|
|
||||||
});
|
|
||||||
return table.put({ id: img, image: req.data });
|
|
||||||
});
|
|
||||||
return await Promise.all(promises);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ export abstract class Client extends EventEmitter {
|
||||||
this.players.push(data.name);
|
this.players.push(data.name);
|
||||||
this.emit("player-joined", data.name);
|
this.emit("player-joined", data.name);
|
||||||
break;
|
break;
|
||||||
case "player-left":
|
case "player-left": {
|
||||||
let idx = this.players.indexOf(data.name);
|
let idx = this.players.indexOf(data.name);
|
||||||
if (idx < 0) {
|
if (idx < 0) {
|
||||||
// Weird
|
// Weird
|
||||||
|
@ -58,8 +58,11 @@ export abstract class Client extends EventEmitter {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.players.splice(idx, 1);
|
this.players.splice(idx, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "password-req":
|
case "password-req":
|
||||||
this.emit("password-required");
|
this.emit("password-required");
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// For most cases, we can just use the kind as event type
|
// For most cases, we can just use the kind as event type
|
||||||
this.emit(data.kind, data);
|
this.emit(data.kind, data);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import GameView from "@/views/Game.vue";
|
||||||
import DraftView from "@/views/Draft.vue";
|
import DraftView from "@/views/Draft.vue";
|
||||||
import Lobby from "@/views/Lobby.vue";
|
import Lobby from "@/views/Lobby.vue";
|
||||||
import RoomView from "@/views/Room.vue";
|
import RoomView from "@/views/Room.vue";
|
||||||
|
import SettingsView from "@/views/Settings.vue";
|
||||||
|
|
||||||
Vue.use(Router);
|
Vue.use(Router);
|
||||||
|
|
||||||
|
@ -48,6 +49,14 @@ export default new Router({
|
||||||
path: "/room",
|
path: "/room",
|
||||||
name: "room",
|
name: "room",
|
||||||
component: RoomView
|
component: RoomView
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/settings",
|
||||||
|
name: "settings",
|
||||||
|
component: SettingsView,
|
||||||
|
meta: {
|
||||||
|
topnav: "settings"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
8
src/shims-worker.d.ts
vendored
Normal file
8
src/shims-worker.d.ts
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// typings/custom.d.ts
|
||||||
|
declare module "worker-loader!*" {
|
||||||
|
class WebpackWorker extends Worker {
|
||||||
|
constructor();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WebpackWorker;
|
||||||
|
}
|
|
@ -3,3 +3,4 @@ export * from "./MockPeer";
|
||||||
export * from "./MockHelper";
|
export * from "./MockHelper";
|
||||||
export * from "./EventHook";
|
export * from "./EventHook";
|
||||||
export * from "./IDBShim";
|
export * from "./IDBShim";
|
||||||
|
export * from "./sync-utils";
|
||||||
|
|
5
src/testing/sync-utils.ts
Normal file
5
src/testing/sync-utils.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export function seconds(ms: number): Promise<void> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(resolve, ms * 1000);
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
import CardPicker from "@/components/DeckBuilder/CardPicker.vue";
|
import CardPicker from "@/components/DeckBuilder/CardPicker.vue";
|
||||||
import { shallowMount } from "@vue/test-utils";
|
import CardImage from "@/components/Cards/CardImage.vue";
|
||||||
|
import { shallowMount, mount } from "@vue/test-utils";
|
||||||
|
import { seconds } from "@/testing";
|
||||||
|
|
||||||
// Generate 10 test cards
|
// Generate 10 test cards
|
||||||
const testCards = new Array(10)
|
const testCards = new Array(10)
|
||||||
|
@ -8,8 +10,8 @@ const testCards = new Array(10)
|
||||||
const testSlots = testCards.map(c => ({ data: c, limit: 3, howmany: 1 }));
|
const testSlots = testCards.map(c => ({ data: c, limit: 3, howmany: 1 }));
|
||||||
|
|
||||||
describe("components/DeckBuilder/CardPicker", () => {
|
describe("components/DeckBuilder/CardPicker", () => {
|
||||||
test("CardPicker correctly creates images for each card", () => {
|
test("CardPicker correctly instances images for each card", () => {
|
||||||
const wrapper = shallowMount(CardPicker, {
|
const wrapper = mount(CardPicker, {
|
||||||
propsData: {
|
propsData: {
|
||||||
rows: 2,
|
rows: 2,
|
||||||
columns: 5,
|
columns: 5,
|
||||||
|
@ -20,6 +22,19 @@ describe("components/DeckBuilder/CardPicker", () => {
|
||||||
expect(cards.contains("img")).toBe(true);
|
expect(cards.contains("img")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("CardImage correctly resolves to an URL after a while", async () => {
|
||||||
|
const wrapper = mount(CardImage, {
|
||||||
|
propsData: {
|
||||||
|
id: "sb1"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let src = wrapper.attributes("src");
|
||||||
|
expect(src).toBe(""); // Should be placeholder but it gets stubbed
|
||||||
|
await seconds(0.5);
|
||||||
|
src = wrapper.attributes("src");
|
||||||
|
expect(src).toMatch(/^https?:|^blob:/);
|
||||||
|
});
|
||||||
|
|
||||||
test("CardPicker correctly aligns items in a grid", () => {
|
test("CardPicker correctly aligns items in a grid", () => {
|
||||||
const wrapper = shallowMount(CardPicker, {
|
const wrapper = shallowMount(CardPicker, {
|
||||||
propsData: {
|
propsData: {
|
||||||
|
|
|
@ -6,7 +6,7 @@ setupIDBShim();
|
||||||
describe("mlpccg/Database", () => {
|
describe("mlpccg/Database", () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
jest.setTimeout(15000);
|
jest.setTimeout(15000);
|
||||||
initDB();
|
await initDB();
|
||||||
await loadSets();
|
await loadSets();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ const testSessionOptions: DraftOptions = {
|
||||||
describe("mlpccg/draft", () => {
|
describe("mlpccg/draft", () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
jest.setTimeout(15000);
|
jest.setTimeout(15000);
|
||||||
initDB();
|
await initDB();
|
||||||
await loadSets();
|
await loadSets();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
188
src/views/Settings.vue
Normal file
188
src/views/Settings.vue
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
<template>
|
||||||
|
<section class="settings">
|
||||||
|
<TopNav class="top" />
|
||||||
|
<section class="settings-box download">
|
||||||
|
<header>
|
||||||
|
<h1>Storage settings</h1>
|
||||||
|
</header>
|
||||||
|
<div class="rows">
|
||||||
|
<article>
|
||||||
|
<div class="name">
|
||||||
|
Card image source
|
||||||
|
</div>
|
||||||
|
<div class="value">
|
||||||
|
{{ imageSource }}
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<b-button
|
||||||
|
class="is-primary"
|
||||||
|
@click="downloadImages"
|
||||||
|
:disabled="cardImageSource == 'local'"
|
||||||
|
>Download images</b-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<b-modal :active.sync="isDownloading" :can-cancel="false">
|
||||||
|
<div class="modal-card" style="width: auto">
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">
|
||||||
|
{{ downloadStatus }}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<section class="modal-card-body">
|
||||||
|
<b-progress
|
||||||
|
size="is-large"
|
||||||
|
type="is-danger"
|
||||||
|
v-if="downloadProgress"
|
||||||
|
:max="downloadProgress.total"
|
||||||
|
:value="downloadProgress.progress"
|
||||||
|
show-value
|
||||||
|
>
|
||||||
|
{{ downloadProgressString }}
|
||||||
|
</b-progress>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "@/assets/scss/_variables.scss";
|
||||||
|
|
||||||
|
.settings {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
grid-column: 1 / end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-box {
|
||||||
|
margin: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
background: rgba($white, 0.1);
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
header {
|
||||||
|
h1 {
|
||||||
|
font-family: $fantasy;
|
||||||
|
font-size: 17pt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rows {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
article {
|
||||||
|
margin: 10px 0;
|
||||||
|
font-size: 12pt;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
& > div {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
border: 1px solid rgba($black, 0.4);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
|
import TopNav from "@/components/Navigation/TopNav.vue";
|
||||||
|
import { TaskRunner } from "@/workers";
|
||||||
|
import { cardImageSource, refreshCardSource } from "@/mlpccg";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: { TopNav }
|
||||||
|
})
|
||||||
|
export default class SettingsView extends Vue {
|
||||||
|
private cardImageSource!: "local" | "remote";
|
||||||
|
private downloadState!: "starting" | "download" | "extract" | null;
|
||||||
|
private downloadProgress!: { progress: number; total: number } | null;
|
||||||
|
|
||||||
|
private downloadImages() {
|
||||||
|
this.downloadState = "starting";
|
||||||
|
const worker = new TaskRunner("downloadCardImages");
|
||||||
|
worker.on("dl-progress", progress => {
|
||||||
|
if (this.downloadState != "download") {
|
||||||
|
this.downloadState = "download";
|
||||||
|
}
|
||||||
|
this.downloadProgress = progress;
|
||||||
|
});
|
||||||
|
worker.on("ex-progress", progress => {
|
||||||
|
if (this.downloadState != "extract") {
|
||||||
|
this.downloadState = "extract";
|
||||||
|
}
|
||||||
|
this.downloadProgress = progress;
|
||||||
|
});
|
||||||
|
worker.on("finish", async _ => {
|
||||||
|
this.downloadState = null;
|
||||||
|
await refreshCardSource();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private data() {
|
||||||
|
return {
|
||||||
|
cardImageSource: cardImageSource(),
|
||||||
|
downloadState: null,
|
||||||
|
downloadProgress: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private get imageSource() {
|
||||||
|
switch (this.cardImageSource) {
|
||||||
|
case "local":
|
||||||
|
return "Local saved copy";
|
||||||
|
case "remote":
|
||||||
|
return "Remote server";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
private get isDownloading(): boolean {
|
||||||
|
return this.downloadState !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get downloadStatus(): string {
|
||||||
|
switch (this.downloadState) {
|
||||||
|
case "starting":
|
||||||
|
return "Starting download...";
|
||||||
|
case "download":
|
||||||
|
return `Downloading image archive (${Math.round(
|
||||||
|
this.downloadProgress!.total / 10485.76
|
||||||
|
) / 100} MB)`;
|
||||||
|
case "extract":
|
||||||
|
return `Extracting images`;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private get downloadProgressString(): string {
|
||||||
|
if (!this.downloadProgress) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
let current = "";
|
||||||
|
let total = "";
|
||||||
|
if (this.downloadState == "extract") {
|
||||||
|
current = `${(this.downloadProgress.progress / 2) | 0}`;
|
||||||
|
total = `${(this.downloadProgress.total / 2) | 0}`;
|
||||||
|
} else {
|
||||||
|
current = `${Math.round(this.downloadProgress.progress / 10485.76) /
|
||||||
|
100}`;
|
||||||
|
total = `${Math.round(this.downloadProgress.total / 10485.76) / 100} MB`;
|
||||||
|
}
|
||||||
|
const percent = Math.round(
|
||||||
|
(this.downloadProgress.progress / this.downloadProgress.total) * 100
|
||||||
|
);
|
||||||
|
return `${percent}% (${current}/${total})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
1
src/workers/index.ts
Normal file
1
src/workers/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from "./runner";
|
17
src/workers/runner.ts
Normal file
17
src/workers/runner.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import EventEmitter from "eventemitter3";
|
||||||
|
|
||||||
|
export class TaskRunner extends EventEmitter {
|
||||||
|
public class: any;
|
||||||
|
public instance: Worker;
|
||||||
|
|
||||||
|
constructor(taskName: string) {
|
||||||
|
super();
|
||||||
|
this.class = require(`worker-loader!@/workers/tasks/${taskName}`);
|
||||||
|
this.instance = new this.class() as Worker;
|
||||||
|
this.instance.addEventListener("error", ev => this.emit("error", ev));
|
||||||
|
this.instance.addEventListener("message", ev => {
|
||||||
|
const message = JSON.parse(ev.data);
|
||||||
|
this.emit(message.type, message.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
72
src/workers/tasks/downloadCardImages.ts
Normal file
72
src/workers/tasks/downloadCardImages.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import { Database, initDB } from "@/mlpccg";
|
||||||
|
import axios from "axios";
|
||||||
|
import JSZip from "jszip";
|
||||||
|
import { send, runAsync } from "../worker-utils";
|
||||||
|
|
||||||
|
async function downloadImages() {
|
||||||
|
if (!Database) {
|
||||||
|
await initDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
let table = Database!.images;
|
||||||
|
|
||||||
|
const itemcount = await table.count();
|
||||||
|
if (itemcount > 1900) {
|
||||||
|
// DB already filled, exit early
|
||||||
|
return "already-done";
|
||||||
|
}
|
||||||
|
|
||||||
|
const zipdata = await axios({
|
||||||
|
url: "https://mcg.zyg.ovh/cards.zip",
|
||||||
|
responseType: "blob",
|
||||||
|
onDownloadProgress: (progressEvent: ProgressEvent) => {
|
||||||
|
send("dl-progress", {
|
||||||
|
progress: progressEvent.loaded,
|
||||||
|
total: progressEvent.total
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const zipfile = await JSZip.loadAsync(zipdata.data);
|
||||||
|
const cards = zipfile.folder("Cards");
|
||||||
|
|
||||||
|
let loadingState = 0;
|
||||||
|
let totalLoading = 0;
|
||||||
|
cards.forEach(async () => {
|
||||||
|
totalLoading += 2;
|
||||||
|
});
|
||||||
|
let waitgroup = new Promise(resolve => {
|
||||||
|
let timer = setInterval(() => {
|
||||||
|
if (loadingState >= totalLoading) {
|
||||||
|
clearInterval(timer);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
cards.forEach(async (filename, filedata) => {
|
||||||
|
const data = await filedata.async("blob");
|
||||||
|
loadingState += 1;
|
||||||
|
send("ex-progress", {
|
||||||
|
progress: loadingState,
|
||||||
|
total: totalLoading
|
||||||
|
});
|
||||||
|
const result = await table.put({ id: filename, image: data });
|
||||||
|
loadingState += 1;
|
||||||
|
send("ex-progress", {
|
||||||
|
progress: loadingState,
|
||||||
|
total: totalLoading
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await waitgroup;
|
||||||
|
|
||||||
|
return "downloaded";
|
||||||
|
}
|
||||||
|
|
||||||
|
runAsync(async () => {
|
||||||
|
try {
|
||||||
|
return await downloadImages();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return new Error(e.message);
|
||||||
|
}
|
||||||
|
});
|
9
src/workers/worker-utils.ts
Normal file
9
src/workers/worker-utils.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export function send(type: string, data?: any) {
|
||||||
|
const ctx: Worker = self as any;
|
||||||
|
ctx.postMessage(JSON.stringify({ type, data }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runAsync(fn: () => Promise<any>) {
|
||||||
|
const val = await fn();
|
||||||
|
send("finish", val);
|
||||||
|
}
|
|
@ -11,21 +11,11 @@
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"types": [
|
"types": ["webpack-env", "jest"],
|
||||||
"webpack-env",
|
|
||||||
"jest"
|
|
||||||
],
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": ["src/*"]
|
||||||
"src/*"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"lib": [
|
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
|
||||||
"esnext",
|
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"scripthost"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
|
@ -34,7 +24,5 @@
|
||||||
"tests/**/*.ts",
|
"tests/**/*.ts",
|
||||||
"tests/**/*.tsx"
|
"tests/**/*.tsx"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": ["node_modules"]
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
}
|
54
yarn.lock
54
yarn.lock
|
@ -770,6 +770,13 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"
|
||||||
integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==
|
integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==
|
||||||
|
|
||||||
|
"@types/jszip@^3.1.6":
|
||||||
|
version "3.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/jszip/-/jszip-3.1.6.tgz#0512574a2b35f3194b41769c56e4b35065e67000"
|
||||||
|
integrity sha512-m8uFcI+O2EupCfbEVQWsBM/4nhbegjOHL7cQgBpM95FeF98kdFJXzy9/8yhx4b3lCRl/gMBhcvyh30Qt3X+XPQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/minimatch@*":
|
"@types/minimatch@*":
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||||
|
@ -5147,6 +5154,11 @@ immediate@^3.2.2:
|
||||||
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c"
|
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c"
|
||||||
integrity sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw=
|
integrity sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw=
|
||||||
|
|
||||||
|
immediate@~3.0.5:
|
||||||
|
version "3.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
|
||||||
|
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
|
||||||
|
|
||||||
import-cwd@^2.0.0:
|
import-cwd@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
|
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
|
||||||
|
@ -6362,6 +6374,16 @@ jsprim@^1.2.2:
|
||||||
json-schema "0.2.3"
|
json-schema "0.2.3"
|
||||||
verror "1.10.0"
|
verror "1.10.0"
|
||||||
|
|
||||||
|
jszip@^3.2.2:
|
||||||
|
version "3.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.2.2.tgz#b143816df7e106a9597a94c77493385adca5bd1d"
|
||||||
|
integrity sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA==
|
||||||
|
dependencies:
|
||||||
|
lie "~3.3.0"
|
||||||
|
pako "~1.0.2"
|
||||||
|
readable-stream "~2.3.6"
|
||||||
|
set-immediate-shim "~1.0.1"
|
||||||
|
|
||||||
killable@^1.0.1:
|
killable@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
|
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
|
||||||
|
@ -6443,6 +6465,13 @@ levn@^0.3.0, levn@~0.3.0:
|
||||||
prelude-ls "~1.1.2"
|
prelude-ls "~1.1.2"
|
||||||
type-check "~0.3.2"
|
type-check "~0.3.2"
|
||||||
|
|
||||||
|
lie@~3.3.0:
|
||||||
|
version "3.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
|
||||||
|
integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
|
||||||
|
dependencies:
|
||||||
|
immediate "~3.0.5"
|
||||||
|
|
||||||
lines-and-columns@^1.1.6:
|
lines-and-columns@^1.1.6:
|
||||||
version "1.1.6"
|
version "1.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
||||||
|
@ -6482,7 +6511,7 @@ loader-utils@^0.2.16:
|
||||||
json5 "^0.5.0"
|
json5 "^0.5.0"
|
||||||
object-assign "^4.0.1"
|
object-assign "^4.0.1"
|
||||||
|
|
||||||
loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
|
loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
|
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
|
||||||
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
|
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
|
||||||
|
@ -7685,7 +7714,7 @@ p-try@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||||
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
||||||
|
|
||||||
pako@~1.0.5:
|
pako@~1.0.2, pako@~1.0.5:
|
||||||
version "1.0.10"
|
version "1.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732"
|
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732"
|
||||||
integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==
|
integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==
|
||||||
|
@ -9098,6 +9127,14 @@ sax@^1.2.4, sax@~1.2.4:
|
||||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||||
|
|
||||||
|
schema-utils@^0.4.0:
|
||||||
|
version "0.4.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187"
|
||||||
|
integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==
|
||||||
|
dependencies:
|
||||||
|
ajv "^6.1.0"
|
||||||
|
ajv-keywords "^3.1.0"
|
||||||
|
|
||||||
schema-utils@^1.0.0:
|
schema-utils@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
|
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
|
||||||
|
@ -9204,6 +9241,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||||
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
|
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
|
||||||
|
|
||||||
|
set-immediate-shim@~1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
|
||||||
|
integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=
|
||||||
|
|
||||||
set-value@^2.0.0, set-value@^2.0.1:
|
set-value@^2.0.0, set-value@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
|
resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
|
||||||
|
@ -10979,6 +11021,14 @@ worker-farm@^1.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
errno "~0.1.7"
|
errno "~0.1.7"
|
||||||
|
|
||||||
|
worker-loader@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-2.0.0.tgz#45fda3ef76aca815771a89107399ee4119b430ac"
|
||||||
|
integrity sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==
|
||||||
|
dependencies:
|
||||||
|
loader-utils "^1.0.0"
|
||||||
|
schema-utils "^0.4.0"
|
||||||
|
|
||||||
wrap-ansi@^2.0.0:
|
wrap-ansi@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
|
||||||
|
|
Loading…
Reference in a new issue