vimp-engine 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/client/components/view/Lobby.js +6 -3
- package/src/client/lib/engineVersion.js +8 -0
- package/src/client/lib/footerLink.js +29 -0
- package/src/client/main.js +12 -7
- package/src/client/style.css +21 -19
- package/src/client/views/gameShell.js +3 -12
- package/src/client/views/includes/auth.pug +4 -3
- package/src/client/views/includes/lobby.pug +5 -1
- package/src/config/lobby.js +2 -1
- package/src/lib/packageLink.js +126 -0
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Publisher from '../../../lib/Publisher.js';
|
|
2
|
-
import { ENGINE_VERSION } from '../../lib/engineVersion.js';
|
|
2
|
+
import { ENGINE_PACKAGE, ENGINE_VERSION } from '../../lib/engineVersion.js';
|
|
3
|
+
import { renderPackageLink } from '../../lib/footerLink.js';
|
|
3
4
|
|
|
4
5
|
// Singleton LobbyView
|
|
5
6
|
|
|
@@ -35,14 +36,16 @@ export default class LobbyView {
|
|
|
35
36
|
this._leaderboardTotal = document.getElementById(elems.leaderboardTotalId);
|
|
36
37
|
this._myPlacement = document.getElementById(elems.myPlacementId);
|
|
37
38
|
|
|
38
|
-
// футер лобби:
|
|
39
|
+
// футер лобби: содержимое статично на всё время жизни вкладки, поэтому
|
|
39
40
|
// пишется здесь, а не через модель и publisher
|
|
40
41
|
const version = document.getElementById(elems.versionId);
|
|
41
42
|
|
|
42
43
|
if (version) {
|
|
43
|
-
version.textContent =
|
|
44
|
+
version.textContent = ENGINE_VERSION;
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
renderPackageLink(document.getElementById(elems.linkId), ENGINE_PACKAGE);
|
|
48
|
+
|
|
46
49
|
// заголовок игры для "<TITLE> TOP-N" (SVG-ориентир) — задаётся controller'ом
|
|
47
50
|
// при выборе игры (сама модель не хранит title манифеста)
|
|
48
51
|
this._gameTitle = '';
|
|
@@ -7,5 +7,13 @@
|
|
|
7
7
|
// бандлу (в npm-тарболле vimp-engine package.json есть всегда, поэтому
|
|
8
8
|
// standalone SDK собирается у игры тем же импортом).
|
|
9
9
|
import pkg from '../../../package.json' with { type: 'json' };
|
|
10
|
+
import { homepageOf } from '../../lib/packageLink.js';
|
|
10
11
|
|
|
11
12
|
export const ENGINE_VERSION = pkg.version;
|
|
13
|
+
|
|
14
|
+
// метаданные для ссылки в футере лобби — та же форма, что мастер отдаёт про
|
|
15
|
+
// пакет игры (GameCatalog), чтобы оба футера строил один resolvePackageLink
|
|
16
|
+
export const ENGINE_PACKAGE = {
|
|
17
|
+
name: pkg.name,
|
|
18
|
+
homepage: homepageOf(pkg),
|
|
19
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { resolvePackageLink } from '../../lib/packageLink.js';
|
|
2
|
+
|
|
3
|
+
// Ссылка в футере лобби и формы входа. Один рендер на оба экрана: лобби
|
|
4
|
+
// подставляет пакет движка, форма входа — пакет игры из манифеста.
|
|
5
|
+
//
|
|
6
|
+
// Данных нет (standalone-манифест без метаданных пакета) — якорь гасится, а не
|
|
7
|
+
// прячется: ячейка футера остаётся пустой, и раскладка space-between не едет.
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {HTMLAnchorElement|null} anchor - якорь футера
|
|
11
|
+
* @param {{name?: string, homepage?: string}|null} pkg - метаданные пакета
|
|
12
|
+
*/
|
|
13
|
+
export function renderPackageLink(anchor, pkg) {
|
|
14
|
+
if (!anchor) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const link = resolvePackageLink(pkg);
|
|
19
|
+
|
|
20
|
+
if (!link) {
|
|
21
|
+
anchor.textContent = '';
|
|
22
|
+
anchor.removeAttribute('href');
|
|
23
|
+
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
anchor.textContent = link.label;
|
|
28
|
+
anchor.href = link.url;
|
|
29
|
+
}
|
package/src/client/main.js
CHANGED
|
@@ -28,6 +28,7 @@ import VoteView from './components/view/Vote.js';
|
|
|
28
28
|
import VoteCtrl from './components/controller/Vote.js';
|
|
29
29
|
import { buildForm, mergeRoomDefaults, bindLiveErrors } from './lib/formBuilder.js';
|
|
30
30
|
import { normalizeAuthParams } from './lib/authParams.js';
|
|
31
|
+
import { renderPackageLink } from './lib/footerLink.js';
|
|
31
32
|
import { createGameActivator } from './lib/gameActivator.js';
|
|
32
33
|
import createAutostart from './lib/autostart.js';
|
|
33
34
|
import { getBootConfig, resolveBootConfig } from './boot.js';
|
|
@@ -422,18 +423,22 @@ socketMethods[PS_AUTH_DATA] = data => {
|
|
|
422
423
|
|
|
423
424
|
document.getElementById('logo').textContent = texts?.title || 'VIMP';
|
|
424
425
|
|
|
425
|
-
//
|
|
426
|
-
// elems приходят из authSchema.elems игрового плагина, и футер каркаса
|
|
427
|
-
// не его забота.
|
|
428
|
-
//
|
|
426
|
+
// футер формы входа: версия и ссылка пакета ИГРЫ. Здесь, а не в AuthView:
|
|
427
|
+
// его elems приходят из authSchema.elems игрового плагина, и футер каркаса
|
|
428
|
+
// — не его забота. Метаданные кладёт в манифест мастер (GameCatalog читает
|
|
429
|
+
// package.json пакета); в standalone-манифесте их может не быть — тогда
|
|
430
|
+
// ячейки пустые, раскладку space-between это не ломает
|
|
429
431
|
const authVersion = document.getElementById('auth-version');
|
|
430
432
|
|
|
431
433
|
if (authVersion) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
authVersion.textContent = packageVersion ? `v${packageVersion}` : '';
|
|
434
|
+
authVersion.textContent = activeGameManifest?.packageVersion ?? '';
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
+
renderPackageLink(document.getElementById('auth-package-link'), {
|
|
438
|
+
name: activeGameManifest?.packageName,
|
|
439
|
+
homepage: activeGameManifest?.packageHomepage,
|
|
440
|
+
});
|
|
441
|
+
|
|
437
442
|
// память клиента (localStorage) + принудительное значение поля с
|
|
438
443
|
// единственным вариантом. Здесь, а не в AuthView: solo-путь ниже отвечает
|
|
439
444
|
// хосту вообще без формы и обязан прийти к тем же значениям
|
package/src/client/style.css
CHANGED
|
@@ -473,7 +473,12 @@ body.hide-cursor {
|
|
|
473
473
|
margin: 4px auto;
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
-
|
|
476
|
+
/* футеры лобби и формы входа: одна и та же полоса из трёх ячеек —
|
|
477
|
+
ссылка на пакет, его версия, копирайт. Оба заполняет JS, поэтому вид
|
|
478
|
+
держится в одном месте (#lobby-footer лежит в секции lobby ниже только
|
|
479
|
+
разметкой) */
|
|
480
|
+
#auth-link,
|
|
481
|
+
#lobby-footer {
|
|
477
482
|
font-size: 9px;
|
|
478
483
|
height: 15px;
|
|
479
484
|
margin: 0;
|
|
@@ -483,26 +488,31 @@ body.hide-cursor {
|
|
|
483
488
|
justify-content: space-between;
|
|
484
489
|
}
|
|
485
490
|
|
|
486
|
-
#auth-link p
|
|
491
|
+
#auth-link p,
|
|
492
|
+
#lobby-footer p {
|
|
487
493
|
margin: 0;
|
|
488
494
|
}
|
|
489
495
|
|
|
490
|
-
#auth-link p a
|
|
496
|
+
#auth-link p a,
|
|
497
|
+
#lobby-footer p a {
|
|
491
498
|
color: #ff0;
|
|
492
499
|
}
|
|
493
500
|
|
|
494
|
-
/*
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
#auth-link p:first-child
|
|
501
|
+
/* позициями заведует space-between, а не nth-child: любая из ячеек бывает
|
|
502
|
+
пустой (у пакета игры может не быть метаданных), и порядковые правила
|
|
503
|
+
тогда съезжали бы */
|
|
504
|
+
#auth-link p:first-child,
|
|
505
|
+
#lobby-footer p:first-child {
|
|
498
506
|
text-align: left;
|
|
499
507
|
}
|
|
500
508
|
|
|
501
|
-
#auth-version
|
|
509
|
+
#auth-version,
|
|
510
|
+
#lobby-version {
|
|
502
511
|
text-align: center;
|
|
503
512
|
}
|
|
504
513
|
|
|
505
|
-
#auth-link p:last-child
|
|
514
|
+
#auth-link p:last-child,
|
|
515
|
+
#lobby-footer p:last-child {
|
|
506
516
|
text-align: right;
|
|
507
517
|
}
|
|
508
518
|
|
|
@@ -783,18 +793,10 @@ body.hide-cursor {
|
|
|
783
793
|
flex-shrink: 0;
|
|
784
794
|
}
|
|
785
795
|
|
|
786
|
-
/*
|
|
787
|
-
|
|
796
|
+
/* полосу футера рисуют общие с #auth-link правила в секции auth; здесь —
|
|
797
|
+
только отступ от панелей */
|
|
788
798
|
#lobby-footer {
|
|
789
|
-
font-size: 9px;
|
|
790
|
-
padding: 0 5px;
|
|
791
799
|
margin: 6px 0 10px;
|
|
792
|
-
text-align: center;
|
|
793
|
-
text-shadow: none;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
#lobby-footer p {
|
|
797
|
-
margin: 0;
|
|
798
800
|
}
|
|
799
801
|
|
|
800
802
|
@media (max-width: 800px) {
|
|
@@ -55,18 +55,9 @@ const SHELL = [
|
|
|
55
55
|
tag: 'div',
|
|
56
56
|
id: 'auth-link',
|
|
57
57
|
children: [
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
{
|
|
62
|
-
tag: 'a',
|
|
63
|
-
attrs: { href: 'https://github.com/lgick/vimp' },
|
|
64
|
-
text: 'GitHub',
|
|
65
|
-
},
|
|
66
|
-
],
|
|
67
|
-
},
|
|
68
|
-
// версия пакета игры (manifest.packageVersion) — текст пишет
|
|
69
|
-
// main.js по AUTH_DATA
|
|
58
|
+
// ссылка и версия пакета ИГРЫ — заполняет main.js по AUTH_DATA
|
|
59
|
+
// из метаданных пакета в манифесте
|
|
60
|
+
{ tag: 'p', children: [{ tag: 'a', id: 'auth-package-link' }] },
|
|
70
61
|
{ tag: 'p', id: 'auth-version' },
|
|
71
62
|
{ tag: 'p', text: `© ${new Date().getFullYear()} VIMP` },
|
|
72
63
|
],
|
|
@@ -15,9 +15,10 @@ div#auth
|
|
|
15
15
|
div#auth-informs
|
|
16
16
|
|
|
17
17
|
div#auth-link
|
|
18
|
+
//- ссылка и версия пакета ИГРЫ (мастер кладёт их в манифест, читая
|
|
19
|
+
//- package.json пакета) — проставляет main.js по AUTH_DATA; без данных
|
|
20
|
+
//- ячейки остаются пустыми
|
|
18
21
|
p
|
|
19
|
-
a
|
|
20
|
-
//- версия пакета игры (manifest.packageVersion): проставляет main.js,
|
|
21
|
-
//- у игры, собранной до появления поля, строка остаётся пустой
|
|
22
|
+
a#auth-package-link
|
|
22
23
|
p#auth-version
|
|
23
24
|
p © #{new Date().getFullYear()} VIMP
|
|
@@ -55,6 +55,10 @@ div#lobby(style='display:none')
|
|
|
55
55
|
span#leaderboard-total
|
|
56
56
|
ol#lobby-leaderboard-list
|
|
57
57
|
p#lobby-my-placement
|
|
58
|
-
//- футер: версию пакета движка проставляет LobbyView
|
|
58
|
+
//- футер: ссылку и версию пакета движка проставляет LobbyView
|
|
59
|
+
//- (engineVersion.js + lib/packageLink.js)
|
|
59
60
|
div#lobby-footer
|
|
61
|
+
p
|
|
62
|
+
a#lobby-link
|
|
60
63
|
p#lobby-version
|
|
64
|
+
p © #{new Date().getFullYear()} VIMP
|
package/src/config/lobby.js
CHANGED
|
@@ -98,8 +98,9 @@ export default {
|
|
|
98
98
|
myPlacementId: 'lobby-my-placement',
|
|
99
99
|
|
|
100
100
|
// футер: версия npm-пакета движка, запечённая в бандл при сборке
|
|
101
|
-
// (client/lib/engineVersion.js)
|
|
101
|
+
// (client/lib/engineVersion.js), и ссылка на его страницу
|
|
102
102
|
versionId: 'lobby-version',
|
|
103
|
+
linkId: 'lobby-link',
|
|
103
104
|
},
|
|
104
105
|
|
|
105
106
|
// создание комнаты (хост в этой же вкладке); лимит игроков/время
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Ссылка на пакет для футеров лобби и формы входа: движок показывает рядом
|
|
2
|
+
// npm-версию, и клик по соседней ячейке должен вести на страницу этого пакета.
|
|
3
|
+
//
|
|
4
|
+
// Источник — `homepage`/`repository` его package.json, но объявлены они не у
|
|
5
|
+
// всех (у @vimp-games/snakes нет ни того, ни другого), а footer без ссылки
|
|
6
|
+
// выглядит поломкой. Поэтому фолбэк — страница пакета на npm: она выводится из
|
|
7
|
+
// одного лишь имени, то есть существует всегда, и сама ведёт на репозиторий,
|
|
8
|
+
// если он объявлен.
|
|
9
|
+
//
|
|
10
|
+
// Модуль изоморфный: мастер зовёт homepageOf для пакета игры (GameCatalog),
|
|
11
|
+
// клиент — для собственного package.json движка (client/lib/engineVersion.js).
|
|
12
|
+
|
|
13
|
+
const NPM_PACKAGE_BASE = 'https://www.npmjs.com/package/';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Сырой адрес проекта из package.json: homepage, иначе repository.
|
|
17
|
+
* @param {Object} pkg - разобранный package.json
|
|
18
|
+
* @returns {string|null} адрес как объявлен (нормализует resolvePackageLink)
|
|
19
|
+
*/
|
|
20
|
+
export function homepageOf(pkg) {
|
|
21
|
+
if (!pkg || typeof pkg !== 'object') {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { homepage, repository } = pkg;
|
|
26
|
+
|
|
27
|
+
if (typeof homepage === 'string' && homepage.trim()) {
|
|
28
|
+
return homepage;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// repository бывает и строкой ("github:user/repo"), и объектом {type, url}
|
|
32
|
+
if (typeof repository === 'string') {
|
|
33
|
+
return repository;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (repository && typeof repository.url === 'string') {
|
|
37
|
+
return repository.url;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Ссылка футера по метаданным пакета.
|
|
45
|
+
* @param {{name?: string, homepage?: string}} [pkg] - имя пакета и сырой homepage
|
|
46
|
+
* @returns {{url: string, label: string}|null} null, если нет даже имени
|
|
47
|
+
*/
|
|
48
|
+
export function resolvePackageLink(pkg) {
|
|
49
|
+
const { name, homepage } = pkg ?? {};
|
|
50
|
+
const declared = normalize(homepage);
|
|
51
|
+
|
|
52
|
+
if (declared) {
|
|
53
|
+
return { url: declared, label: labelFor(declared) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof name === 'string' && name.trim()) {
|
|
57
|
+
return { url: `${NPM_PACKAGE_BASE}${name.trim()}`, label: 'npm' };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// приводит объявленный адрес к https-виду; всё, что после нормализации не
|
|
64
|
+
// http(s) (git@ без известной формы, file:, мусор), — не ссылка, а фолбэк
|
|
65
|
+
function normalize(raw) {
|
|
66
|
+
if (typeof raw !== 'string') {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let url = raw.trim();
|
|
71
|
+
|
|
72
|
+
if (!url) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// npm-шорткаты: "github:user/repo", "gitlab:...", "bitbucket:..."
|
|
77
|
+
const shorthand = /^(github|gitlab|bitbucket):([\w.-]+\/[\w.-]+)$/.exec(url);
|
|
78
|
+
|
|
79
|
+
if (shorthand) {
|
|
80
|
+
const hosts = {
|
|
81
|
+
github: 'github.com',
|
|
82
|
+
gitlab: 'gitlab.com',
|
|
83
|
+
bitbucket: 'bitbucket.org',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return `https://${hosts[shorthand[1]]}/${shorthand[2]}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
url = url.replace(/^git\+/, '');
|
|
90
|
+
|
|
91
|
+
// scp-форма git@host:user/repo — двоеточие здесь разделитель, не порт
|
|
92
|
+
const scp = /^git@([^:/]+):(.+)$/.exec(url);
|
|
93
|
+
|
|
94
|
+
if (scp) {
|
|
95
|
+
url = `https://${scp[1]}/${scp[2]}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
url = url
|
|
99
|
+
.replace(/^ssh:\/\/git@/, 'https://')
|
|
100
|
+
.replace(/^ssh:\/\//, 'https://')
|
|
101
|
+
.replace(/^git:\/\//, 'https://');
|
|
102
|
+
|
|
103
|
+
if (!/^https?:\/\//.test(url)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
url
|
|
109
|
+
.replace(/\.git(?=$|[#?])/, '')
|
|
110
|
+
// npm сам дописывает "#readme" в homepage при генерации package.json
|
|
111
|
+
.replace(/#readme$/, '')
|
|
112
|
+
.replace(/\/+$/, '')
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function labelFor(url) {
|
|
117
|
+
if (/^https?:\/\/(www\.)?github\.com\//.test(url)) {
|
|
118
|
+
return 'GitHub';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (url.startsWith(NPM_PACKAGE_BASE)) {
|
|
122
|
+
return 'npm';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return 'Website';
|
|
126
|
+
}
|