import AFRAME from 'aframe' import { CylindricalGrid, Cylinder } from '../../../classes/aframe/layout/GridUtils' export const ComponentName = 'grid' export interface GridSystemData { } export interface GridSystemProps { gridRotation: (x: number, c: number) => number, gridOffsetY: (r: number, h: number) => number, getSource: (m:any) => string, gridRotationString:(gridCellsPerRow: number, c: number) => string, gridRotationObj:(gridCellsPerRow: number, c: number) => {x: number, y: number, z: number}, gridOffsetString:(rows: number, cellHeight: number) => string gridOffsetObj:(rows: number, cellHeight: number) => {x: number, y: number, z: number}, updateLayout:(g: any, c: AFRAME.Entity[], s: number, e: number) => void } export const GridSystemSchema: AFRAME.Schema = { } export const GridSystemDef: AFRAME.SystemDefinition = { schema: GridSystemSchema, data: { } as GridSystemData, init () { }, play() { }, pause() { }, gridRotation(gridCellsPerRow: number, columns: number) { return (columns === 1) ? Math.PI : Math.PI - (Math.PI * 2 / (gridCellsPerRow ?? gridCellsPerRow)) * 2 }, gridOffsetY(rows: number, cellHeight: number) { return (1 - (rows ?? rows) / 2) * (cellHeight ?? cellHeight) }, getSource(media:any): string { return media.thumbnail_url && media.thumbnail_url.length > 0 ? media.thumbnail_url : '#placeholder' }, gridRotationString(gridCellsPerRow: number, columns: number): string { return '0 ' + this.gridRotation(gridCellsPerRow, columns) + ' 0' }, gridRotationObj(gridCellsPerRow: number, columns: number): {x: number, y: number, z: number} { return { x: 0, y: this.gridRotation(gridCellsPerRow, columns), z: 0 } }, gridOffsetString(rows: number, cellHeight: number): string { return '0 ' + this.gridOffsetY(rows, cellHeight) + ' 0' }, gridOffsetObj(rows: number, cellHeight: number): {x: number, y: number, z: number} { return { x: 0, y: this.gridOffsetY(rows, cellHeight), z: 0 } }, updateLayout(gridShape: any, children: AFRAME.Entity[], pageStart: number, pageEnd: number) { children.forEach((cell: AFRAME.Entity, i: number) => { const cellActive = (i >= pageStart && i < pageEnd) cell.setAttribute('media-cell', { active: cellActive }) const gridIndex = i - pageStart const pos = gridShape.cellPosition(gridIndex) cell.object3D.position.set(pos.x, pos.y, pos.z) const rot = gridShape.cellRotation(gridIndex) cell.object3D.rotation.set(rot.x, rot.y + Math.PI, rot.z) }) } } export interface GridData { [key: string]: any, gridCellsPerRow?: number, cellHeight?: number, radius?: number, rows?: number, columns?: number, cellWidth?: number, cellContentHeight?: number, page?: number, pageLeftEvent?: string, pageRightEvent?: string, // TODO : media type } export const GridComponentSchema: AFRAME.MultiPropertySchema = { gridCellsPerRow: { default: 28 }, cellHeight: { default: 0.6 }, radius: { default: 6 }, rows: { default: 3 }, columns: { default: 5 }, cellWidth: { default: 1 }, cellContentHeight: { default: 0.5 }, page: { default: 0 }, pageLeftEvent: { default: 'pageleft' }, pageRightEvent: { default: 'pageright' } } export interface GridProps { initGrid: () => void, cylinder: Cylinder, cylindricalGrid: CylindricalGrid, cellsPerPage: number, totalCells: number, pages: number, updateLayout: () => void, updateChildren: () => void, children: AFRAME.Entity[], addPaginators: () => void, createPaginator: (side: string) => AFRAME.Entity, updatePaginators: () => void, pageLeftHandler: () => void, pageRightHandler: () => void, canPageLeft: boolean, canPageRight: boolean, addHandlers: () => void, removeHandlers: () => void } export const GridComponent: AFRAME.ComponentDefinition = { schema: GridComponentSchema, data: { } as GridData, cylinder: {} as Cylinder, cylindricalGrid: {} as CylindricalGrid, cellsPerPage: 0, totalCells: 0, pages: 0, children: [] as AFRAME.Entity[], canPageLeft: false, canPageRight: false, init () { this.cellsPerPage = this.data.rows * this.data.columns this.cylinder = new Cylinder(this.data.cellsPerRow, this.data.cellHeight, this.data.cellWidth, this.data.radius) this.cylindricalGrid = new CylindricalGrid(this.data.cellsPerRow, this.data.cellHeight, this.data.cellWidth, this.data.radius, this.data.rows, this.data.columns) if (this.el.sceneEl?.hasLoaded) this.initGrid() else this.el.sceneEl?.addEventListener('loaded', this.initGrid.bind(this)) }, play() { this.addHandlers() }, pause() { this.removeHandlers() }, update(oldData: GridData) { const changedData = Object.keys(this.data).filter(x => this.data[x] !== oldData[x]) if (changedData.includes('page')) { this.updateLayout() this.updatePaginators() } }, initGrid() { this.el.classList.add('grid-cylinder') const rotObj = (this.system as AFRAME.SystemDefinition).gridRotationObj(this.data.gridCellsPerRow, this.data.columns) this.el.object3D.rotation.set(rotObj.x, rotObj.y, rotObj.z) const posObj = (this.system as AFRAME.SystemDefinition).gridOffsetObj(this.data.rows, this.data.cellHeight) this.el.object3D.position.set(posObj.x, posObj.y, posObj.z) this.updateLayout() this.addPaginators() this.updatePaginators() }, updateLayout() { this.updateChildren() if (this.system) { (this.system as AFRAME.SystemDefinition).updateLayout( this.cylindricalGrid, this.children, this.data.page * this.cellsPerPage, (this.data.page + 1) * this.cellsPerPage ) } }, updateChildren() { this.children = ((this.el as AFRAME.Entity) as any).getChildEntities().filter( // eslint-disable-next-line no-prototype-builtins (el: AFRAME.Entity) => { return el.components.hasOwnProperty('grid-cell') }) this.totalCells = this.children.length this.pages = Math.ceil(this.totalCells / this.cellsPerPage) }, updatePaginators() { const leftDisabled = this.data.page === 0 const rightDisabled = (this.data.page + 1) * this.cellsPerPage >= this.totalCells const leftPaginator = this.el.querySelector('.left-paginator') const rightPaginator = this.el.querySelector('.right-paginator') leftPaginator?.setAttribute('highlight', { disabled: leftDisabled }) rightPaginator?.setAttribute('highlight', { disabled: rightDisabled }) }, createPaginator (side: string) { const paginatorEl = document.createElement('a-arrow') paginatorEl.classList.add(side + '-paginator') paginatorEl.setAttribute('direction', side) paginatorEl.setAttribute('width', 0.35) paginatorEl.setAttribute('height', 0.2) paginatorEl.setAttribute('clickable', { clickevent: 'page' + side }) paginatorEl.setAttribute('highlight', { type: 'color', borderbaseopacity: 0.7, disabledopacity: 0.2, color: 0xe8f1ff }) const col = side === 'left' ? this.data.columns : 0 const pos = this.cylinder.cellPosition(col, 0) paginatorEl.object3D.position.set(pos.x - 0.1525, pos.y, pos.z) const rot = this.cylinder.cellRotation(col, false) paginatorEl.object3D.rotation.set(rot.x, rot.y + Math.PI, rot.z) return paginatorEl }, addPaginators() { const pageLeftEl = this.createPaginator('left') this.el.appendChild(pageLeftEl) const pageRightEl = this.createPaginator('right') this.el.appendChild(pageRightEl) }, pageLeftHandler() { if (this.data.page > 0) { this.el.setAttribute('page', this.data.page - 1) } }, pageRightHandler() { if (this.data.page < this.pages) { this.el.setAttribute('page', this.data.page + 1) } }, addHandlers: function() { this.el.addEventListener(this.data.pageLeftEvent, this.pageLeftHandler.bind(this)) this.el.addEventListener(this.data.pageRightEvent, this.pageRightHandler.bind(this)) }, removeHandlers: function() { this.el.removeEventListener(this.data.pageLeftEvent, this.pageLeftHandler) this.el.removeEventListener(this.data.pageRightEvent, this.pageRightHandler) } } export const GridPrimitive: AFRAME.PrimitiveDefinition = { defaultComponents: { ComponentName: {} }, deprecated: false, mappings: { id: ComponentName + '.id', 'grid-cells-per-row': ComponentName + '.gridCellsPerRow', radius: ComponentName + '.radius', columns: ComponentName + '.columns', rows: ComponentName + '.rows', 'cell-height': ComponentName + '.cellHeight', 'cell-width': ComponentName + '.cellWidth', 'cell-content-height': ComponentName + '.cellContentHeight', page: ComponentName + '.page' } } const ComponentSystem = { name: ComponentName, system: GridSystemDef, component: GridComponent, primitive: GridPrimitive } export default ComponentSystem