UNPKG

@vrspace/babylonjs

Version:

vrspace.org babylonjs client

400 lines (358 loc) 15 kB
import { VRSPACE } from '../../client/vrspace.js'; import { VRSPACEUI } from '../vrspace-ui.js'; import { VRSpaceAPI } from '../../client/rest-api.js'; import { GroupsApi } from '../../client/openapi/api/GroupsApi.js'; import { Form } from '../widget/form.js'; import { UserGroup } from '../../client/openapi/model/UserGroup.js'; import { GroupMember } from '../../client/openapi/model/GroupMember.js'; import { FormArea } from '../widget/form-area.js'; import { World } from '../../world/world.js'; import { GroupSettingsForm } from './group-settings-form.js'; import { UserInviteForm } from '../widget/user-invite-form.js'; import { InviteInfoForm } from './invite-info-form.js'; import { ListMembersForm } from './list-members-form.js'; import { GroupHelper } from './group-helper.js'; export class ListGroupsForm extends Form { constructor(scene, invites, groups, groupDeleteCallback, refreshCallback) { super(); this.scene = scene; /** @type { GroupMember[] } */ this.invites = invites; /** @type { UserGroup[] } */ this.groups = groups; /** @type { UserGroup[] } */ this.table = Array(this.invites.length + this.groups.length); this.contentBase = VRSPACEUI.contentBase; this.privateIcon = this.contentBase + "/content/icons/private-message.png"; this.leaveGroupIcon = this.contentBase + "/content/icons/user-group-minus.png"; this.groupSettingIcon = this.contentBase + "/content/icons/user-group-settings.png"; this.groupInviteIcon = this.contentBase + "/content/icons/user-group-plus.png"; this.groupInfoIcon = this.contentBase + "/content/icons/user-group-info.png"; this.groupAcceptIcon = this.contentBase + "/content/icons/tick.png"; this.groupDeleteIcon = this.contentBase + "/content/icons/delete.png"; this.groupDeleteCallback = groupDeleteCallback; this.refreshCallback = refreshCallback; this.stackVertical = true; this.stackHorizontal = true; /** @type {GroupsApi} */ this.groupApi = VRSpaceAPI.getInstance().endpoint.groups; /** @type {number} */ this.activeRow = null; this.activeButton = null; this.pointerTracker = null; this.style = null; /** @type {GroupSettingsForm} */ this.settingsForm = null; /** @type {UserInviteForm} */ this.inviteForm = null; this.selectionPredicate = mesh => mesh == this.plane; this.groupEventListener = null; } init() { this.createPanel(); this.grid = new BABYLON.GUI.Grid(); this.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; this.grid.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; this.grid.paddingLeftInPixels = 10; this.grid.paddingTopInPixels = 10; this.grid.paddingBottomInPixels = 10; /* // GUI pointer observables work in mozilla, not in chrome // CHECKME may just require selection predicate this.grid.isPointerBlocker = true; this.grid.onPointerEnterObservable.add(()=>this.pointerEnter()); this.grid.onPointerOutObservable.add(()=>this.pointerOut()); this.grid.onPointerClickObservable.add(()=>this.pointerClick()); // so, use scene facilities instead */ this.pointerTracker = this.scene.onPointerObservable.add((pointerInfo) => { if (pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh) { if (pointerInfo.pickInfo.pickedMesh == this.plane) { let coord = pointerInfo.pickInfo.getTextureCoordinates(); // width of group text in column 2 if (coord.x > 0.1 && coord.x < 0.8) { if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERMOVE) { let row = Math.floor((1 - coord.y) * (this.table.length)); this.pointerEvent(row); } else if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERDOWN && this.groups.length > 0) { this.pointerClick(); } } } else { this.pointerOut(); } } else { this.pointerOut(); } }); World.lastInstance.addSelectionPredicate(this.selectionPredicate); this.grid.addColumnDefinition(0.05); this.grid.addColumnDefinition(0.05); this.grid.addColumnDefinition(0.7); this.grid.addColumnDefinition(0.05); this.grid.addColumnDefinition(0.05); this.grid.addColumnDefinition(0.05); this.grid.addColumnDefinition(0.05); let index = 0; this.invites.forEach(invite => { this.grid.addRowDefinition(this.heightInPixels, true); let indexLabel = this.textBlock(index + 1); indexLabel.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT; this.grid.addControl(indexLabel, index, 0); let privateImage = this.makeIcon("private", this.privateIcon); this.grid.addControl(privateImage, index, 1); privateImage.isVisible = !invite.group.public; this.grid.addControl(this.textBlock(invite.group.name), index, 2); let infoButton = this.submitButton("info", () => this.inviteInfo(invite), this.groupInfoIcon); //infoButton.isVisible = false; infoButton.background = this.background; this.grid.addControl(infoButton, index, 4); let acceptButton = this.submitButton("accept", () => this.inviteAccept(invite), this.groupAcceptIcon); this.grid.addControl(acceptButton, index, 5); //acceptButton.isVisible = false; let rejectButton = this.submitButton("reject", () => this.inviteReject(invite), this.groupDeleteIcon); rejectButton.background = this.cancelColor; this.grid.addControl(rejectButton, index, 6); //rejectButton.isVisible = false; this.table[index] = invite.group; this.table[index].isInvite = true; index++; }); this.groups.forEach(group => { this.grid.addRowDefinition(this.heightInPixels, true); let indexLabel = this.textBlock(index + 1); indexLabel.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT; this.grid.addControl(indexLabel, index, 0); let privateImage = this.makeIcon("private", this.privateIcon); this.grid.addControl(privateImage, index, 1); privateImage.isVisible = !group.public; this.grid.addControl(this.textBlock(group.name), index, 2); this.grid.addControl(this.textBlock(group.unread), index, 3); let button; if (group.isOwned) { button = this.submitButton("settings", () => this.groupSettings(group), this.groupSettingIcon); } else { button = this.submitButton("info", () => this.groupInfo(group), this.groupInfoIcon); } button.isVisible = false; button.background = this.background; this.grid.addControl(button, index, 4); let inviteButton = this.submitButton("invite", () => this.groupInvite(group), this.groupInviteIcon); this.grid.addControl(inviteButton, index, 5); inviteButton.isVisible = false; let deleteButton = this.submitButton("delete", () => this.groupDeleteCallback(group), this.groupDeleteIcon); deleteButton.background = this.cancelColor; this.grid.addControl(deleteButton, index, 6); deleteButton.isVisible = false; this.table[index] = group; index++; }); this.panel.addControl(this.grid); this.textureWidth = 1280; this.textureHeight = this.heightInPixels * (Math.max(this.grid.rowCount, 1)) + this.grid.paddingTopInPixels + this.grid.paddingBottomInPixels; this.groupEventListener = VRSPACE.addGroupListener(event => { if (event.message) { let groupIndex = this.groups.findIndex(group => group.id == event.message.group.id); if (groupIndex >= 0 && typeof this.groups[groupIndex].chatlog == "undefined") { this.groups[groupIndex].unread++; let row = groupIndex + this.invites.length; this.grid.getChildrenAt(row, 3)[0].text = this.groups[groupIndex].unread; } } else if (event.invite) { // CHECKME: what to do with invites? if (this.groups.find(group => group.id == event.invite.group.id)) { console.error("Received invalid invite:", event.invite); } else { this.refreshCallback(); } } else if (event.ask) { console.log("TODO asked to join:", event.ask); } else if (event.allowed) { console.log("TODO allowed to join:", event.ask); } }); } pointerEvent(row) { if (row !== this.activeRow) { this.activeRow = row; if (this.activeButtons) { this.activeButtons.forEach(button => button.isVisible = false); this.activeButtons = null; } if (this.activeText) { this.activeText.fontStyle = null; this.activeText = null; } let group = this.table[row]; if (group.isInvite) { // not changing anything for invites return; } let button = this.grid.getChildrenAt(row, 4)[0]; let inviteButton = this.grid.getChildrenAt(row, 5)[0]; let deleteButton = this.grid.getChildrenAt(row, 6)[0]; button.isVisible = true; inviteButton.isVisible = (group.isOwned || group.public || group.isInvite); deleteButton.isVisible = group.isOwned || group.isInvite; this.activeButtons = [button, inviteButton, deleteButton]; this.activeText = this.grid.getChildrenAt(row, 2)[0]; this.activeText.fontStyle = "bold"; } } pointerOut() { if (this.activeRow != null) { console.log("pointer out"); if (this.activeButtons) { this.activeButtons.forEach(button => button.isVisible = false); this.activeButtons = null; } if (this.activeText) { this.activeText.fontStyle = null; this.activeText = null; } this.grid.onPointerMoveObservable.remove(this.pointerTracker); this.activeRow = null; } } inviteInfo(invite) { let inviteForm = new InviteInfoForm(invite, accepted => { if (accepted) { this.inviteAccept(invite); } else { this.inviteReject(invite); } VRSPACEUI.hud.clearRow(); VRSPACEUI.hud.showButtons(true); }); inviteForm.init(); VRSPACEUI.hud.showButtons(false); VRSPACEUI.hud.newRow(); VRSPACEUI.hud.addForm(inviteForm, 1024, 64); } async inviteAccept(invite) { await this.groupApi.accept(invite.group.id) this.invites.splice(this.invites.findIndex(i => i == invite), 1); this.refreshCallback(); } async inviteReject(invite) { await this.groupApi.leave(invite.group.id) this.invites.splice(this.invites.findIndex(i => i == invite), 1); this.refreshCallback(); } pointerClick() { let group = this.table[this.activeRow]; console.log("read messages of: " + group.isInvite, group); if (typeof group.chatlog == "undefined") { GroupHelper.attachChatlog(group, this.scene, this.stackVertical, this.stackHorizontal); // this is not safe to do after listUnreadMessages returns - activeRow may have be changed this.grid.getChildrenAt(this.activeRow, 3)[0].text = ""; group.unread = 0; GroupHelper.showUnread(group); } } async groupLeave(group) { await this.groupApi.leave(group.id) this.refreshCallback(); } closeMembersForm() { if (this.listMembersForm != null) { this.listMembersForm.dispose(); this.listArea.dispose(); this.listMembersForm = null; this.listArea = null; } } async memberList(group, isOwner) { this.closeMembersForm(); this.listMembersForm = new ListMembersForm( this.scene, group, isOwner, () => this.closeMembersForm(), () => this.memberList(group, isOwner) ); await this.listMembersForm.init(); this.listArea = new FormArea(this.scene, this.listMembersForm); this.listArea.size = .2; this.listArea.show(1280, this.listMembersForm.heightInPixels * (this.listMembersForm.table.length + 3)); this.listArea.attachToHud(); this.listArea.detach(.7); this.listArea.group.billboardMode = BABYLON.Mesh.BILLBOARDMODE_Y; } groupCommon(group, isOwner, callback) { if (this.settingsForm != null) { this.settingsForm.dispose(); this.settingsArea.dispose(); } this.settingsForm = new GroupSettingsForm(group, isOwner, (ok) => { this.settingsArea.dispose(); callback(ok); }, () => this.memberList(group, isOwner)); this.settingsForm.init(); this.settingsArea = new FormArea(this.scene, this.settingsForm); this.settingsArea.size = .2; this.settingsArea.show(1280, 256); this.settingsArea.attachToHud(); this.settingsArea.detach(.8); this.settingsArea.group.billboardMode = BABYLON.Mesh.BILLBOARDMODE_Y; } groupInfo(group) { this.groupCommon(group, false, (leave) => { if (leave) { this.groupLeave(group); } }); } groupSettings(group) { this.groupCommon(group, true, (ok) => { if (ok) { let isPublic = this.settingsForm.publicCheckbox.isChecked; let groupName = this.settingsForm.nameInput.text.trim() group.name = groupName; group.public = isPublic; this.groupApi.update(group).then(() => { let icon = this.grid.getChildrenAt(this.activeRow, 1)[0]; icon.isVisible = !this.settingsForm.publicCheckbox.isChecked; let groupNameLabel = this.grid.getChildrenAt(this.activeRow, 2)[0]; groupNameLabel.text = groupName; }); } }); } /** * Invite a user to a group: pop up UserInviteForm to select/enter the user, then send the invite. * @param {UserGroup} group */ groupInvite(group) { VRSPACEUI.hud.showButtons(false); VRSPACEUI.hud.newRow(); this.inviteForm = new UserInviteForm(this.scene, (ok, userId) => { if (ok) { this.groupApi.invite(group.id, userId); } VRSPACEUI.hud.clearRow(); VRSPACEUI.hud.showButtons(true); this.trackPointer = true; }); this.inviteForm.init(); this.inviteForm.addToHud(); this.trackPointer = false; } dispose() { super.dispose(); World.lastInstance.removeSelectionPredicate(this.selectionPredicate); if (this.pointerTracker) { this.scene.onPointerObservable.remove(this.pointerTracker); this.pointerTracker = null } if (this.groupEventListener) { VRSPACE.removeGroupListener(this.groupEventListener); this.groupEventListener = null; } this.closeMembersForm(); if (this.settingsForm != null) { this.settingsForm.dispose(); this.settingsArea.dispose(); } } }