powergrid-viewer 2.1.0 → 2.1.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "powergrid-viewer",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/boardgamers/powergrid.git",
@@ -2820,7 +2820,8 @@ text {
2820
2820
  width: 100%;
2821
2821
  display: grid;
2822
2822
  gap: 8px;
2823
- margin-top: 8px;
2823
+ margin: 8px 0;
2824
+ padding-bottom: env(safe-area-inset-bottom);
2824
2825
  align-items: start;
2825
2826
  }
2826
2827
  .journal-and-chat > * {
@@ -32,6 +32,7 @@ export default class InlineLog extends Vue {
32
32
  @Prop({ default: () => [] }) entries!: string[];
33
33
  @Prop({ default: '' }) mapName!: string;
34
34
  follow = true;
35
+ private feedObserver?: ResizeObserver;
35
36
  get illustratedEntries() {
36
37
  return this.entries.map((entry) => {
37
38
  const parts: { html?: string; plant?: PowerPlant }[] = [];
@@ -54,8 +55,15 @@ export default class InlineLog extends Vue {
54
55
  } cities`;
55
56
  }
56
57
  mounted() {
58
+ this.feedObserver = new ResizeObserver(() => {
59
+ if (this.follow) this.scrollToLatest();
60
+ });
61
+ this.feedObserver.observe(this.$refs.feed as HTMLElement);
57
62
  this.scrollToLatest();
58
63
  }
64
+ beforeDestroy() {
65
+ this.feedObserver?.disconnect();
66
+ }
59
67
  @Watch('entries') changed() {
60
68
  if (this.follow) {
61
69
  this.$nextTick(this.scrollToLatest);
@@ -63,13 +71,13 @@ export default class InlineLog extends Vue {
63
71
  }
64
72
  scrollToLatest() {
65
73
  const feed = this.$refs.feed as HTMLElement;
66
- if (feed) {
74
+ if (feed?.clientHeight) {
67
75
  feed.scrollTop = feed.scrollHeight;
68
76
  }
69
77
  }
70
78
  onScroll() {
71
79
  const feed = this.$refs.feed as HTMLElement;
72
- this.follow = feed.scrollHeight - feed.scrollTop - feed.clientHeight < 32;
80
+ if (feed.clientHeight) this.follow = feed.scrollHeight - feed.scrollTop - feed.clientHeight < 32;
73
81
  }
74
82
  }
75
83
  </script>
package/src/game-chat.ts CHANGED
@@ -32,6 +32,14 @@ export function mountGameChat(emitter: ChatEmitter, host: Element): void {
32
32
  .bgs-game-chat .chat-status:empty{display:none}
33
33
  .chat-shortcut{position:fixed;left:16px;bottom:max(16px,env(safe-area-inset-bottom));z-index:900;padding:7px 12px;border:1px solid #6a8589;border-radius:3px;background:#263521;color:#fff;font:600 14px Arial,sans-serif;cursor:pointer;box-shadow:0 2px 6px #0003}
34
34
  .chat-shortcut[hidden]{display:none}
35
+ .game-feed-tabs{display:none}
36
+ @media(max-width:700px){
37
+ .game-feed-tabs{display:flex;gap:4px;margin-top:8px}
38
+ .game-feed-tabs button{border:1px solid #a2aa82;border-radius:3px 3px 0 0;padding:7px 14px;background:#e7ead7;color:#263521;font:600 14px Arial,sans-serif;cursor:pointer}
39
+ .game-feed-tabs button[aria-pressed="true"]{background:#f3f0dc;border-bottom:3px solid #526f32}
40
+ .game-feed-tabs button:focus-visible{outline:2px solid #247d8c;outline-offset:2px}
41
+ .journal-and-chat[data-feed="chat"]>.inline-game-log,.journal-and-chat[data-feed="journal"]>.chat-host{display:none}
42
+ }
35
43
  .chat-shortcut:hover{background:#315966}
36
44
  .chat-shortcut:focus-visible{outline:2px solid #fff;outline-offset:2px}
37
45
 
@@ -40,6 +48,29 @@ export function mountGameChat(emitter: ChatEmitter, host: Element): void {
40
48
  const slot = host.querySelector('.chat-host');
41
49
  if (slot) slot.append(panel);
42
50
  else host.insertAdjacentElement('afterend', panel);
51
+ const feeds = host.querySelector('.journal-and-chat') as HTMLElement | null;
52
+ const tabs = document.createElement('nav');
53
+ tabs.className = 'game-feed-tabs';
54
+ tabs.setAttribute('aria-label', 'Chat and journal');
55
+ const chatTab = document.createElement('button');
56
+ const journalTab = document.createElement('button');
57
+ chatTab.type = journalTab.type = 'button';
58
+ chatTab.textContent = 'Chat';
59
+ journalTab.textContent = 'Journal';
60
+ tabs.append(chatTab, journalTab);
61
+ if (feeds) feeds.insertAdjacentElement('beforebegin', tabs);
62
+ function selectFeed(feed: 'chat' | 'journal'): void {
63
+ if (feeds) feeds.dataset.feed = feed;
64
+ chatTab.setAttribute('aria-pressed', String(feed === 'chat'));
65
+ journalTab.setAttribute('aria-pressed', String(feed === 'journal'));
66
+ }
67
+ selectFeed('chat');
68
+ chatTab.onclick = () => {
69
+ selectFeed('chat');
70
+ panel.open = true;
71
+ requestAnimationFrame(read);
72
+ };
73
+ journalTab.onclick = () => selectFeed('journal');
43
74
  const list = panel.querySelector('.chat-messages') as HTMLDivElement;
44
75
  const input = panel.querySelector('input') as HTMLInputElement;
45
76
  const button = panel.querySelector('button') as HTMLButtonElement;
@@ -70,7 +101,7 @@ export function mountGameChat(emitter: ChatEmitter, host: Element): void {
70
101
  shortcut.type = 'button';
71
102
  shortcut.className = 'chat-shortcut';
72
103
  shortcut.hidden = true;
73
- panel.insertAdjacentElement('afterend', shortcut);
104
+ (feeds || panel).insertAdjacentElement('afterend', shortcut);
74
105
  let chatVisible = false;
75
106
  function updateShortcut(): void {
76
107
  const count = unread.size;
@@ -78,9 +109,11 @@ export function mountGameChat(emitter: ChatEmitter, host: Element): void {
78
109
  shortcut.textContent = label;
79
110
  shortcut.setAttribute('aria-label', `Open ${label}`);
80
111
  summary.textContent = label;
112
+ chatTab.textContent = label;
81
113
  shortcut.hidden = chatVisible;
82
114
  }
83
115
  shortcut.onclick = () => {
116
+ selectFeed('chat');
84
117
  panel.open = true;
85
118
  requestAnimationFrame(() => {
86
119
  const firstUnread = Array.from(list.children).find((row) =>
@@ -117,6 +150,7 @@ export function mountGameChat(emitter: ChatEmitter, host: Element): void {
117
150
  return;
118
151
  }
119
152
  const bounds = list.getBoundingClientRect();
153
+ if (!bounds.width || !bounds.height) return;
120
154
  const visible = Array.from(list.children).filter((el) => {
121
155
  const r = el.getBoundingClientRect();
122
156
  return r.bottom <= Math.min(bounds.bottom, window.innerHeight) + 1 && r.top >= Math.max(bounds.top, 0);
@@ -267,7 +301,7 @@ export function mountGameChat(emitter: ChatEmitter, host: Element): void {
267
301
  }
268
302
  };
269
303
  list.onscroll = () => {
270
- following = list.scrollHeight - list.scrollTop - list.clientHeight < 32;
304
+ if (list.clientHeight) following = list.scrollHeight - list.scrollTop - list.clientHeight < 32;
271
305
  read();
272
306
  };
273
307
  panel.ontoggle = () => {
@@ -280,10 +314,12 @@ export function mountGameChat(emitter: ChatEmitter, host: Element): void {
280
314
  window.addEventListener('focus', read);
281
315
  window.addEventListener('resize', read);
282
316
  const sizing = new ResizeObserver(() => {
317
+ if (following && list.clientHeight) list.scrollTop = list.scrollHeight;
283
318
  updateShortcut();
284
319
  read();
285
320
  });
286
321
  sizing.observe(panel);
322
+ sizing.observe(list);
287
323
  document.addEventListener('visibilitychange', read);
288
324
  status.textContent = reason;
289
325
  controls();