diff --git a/src/assets/images/account.svg b/src/assets/images/account.svg index a39a815fd97c9157c2de36ea7fd7fc98888cb58f..f3edafbfaceda74d3ec551e0a146a63843d8177e 100755 --- a/src/assets/images/account.svg +++ b/src/assets/images/account.svg @@ -4,7 +4,7 @@ <g id="Canvas" transform="translate(-42 105)"> <g id="account"> <g id="Vector"> -<use xlink:href="#path0_fill" transform="translate(42 -105)" fill="#FFFFFF"/> +<use xlink:href="#path0_fill" transform="translate(42 -105)" fill="#FF3464"/> </g> </g> </g> diff --git a/src/assets/images/daydream_entry.svg b/src/assets/images/daydream_entry.svg index 1b61b29ffe41f915c27ab8e6d42fed323ce96530..01ec49e30166f83ab63df19e9aa43896bba1ac7c 100755 --- a/src/assets/images/daydream_entry.svg +++ b/src/assets/images/daydream_entry.svg @@ -1,39 +1,38 @@ -<svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M88.5 45C88.5 69.0244 69.0244 88.5 45 88.5C20.9756 88.5 1.5 69.0244 1.5 45C1.5 20.9756 20.9756 1.5 45 1.5C69.0244 1.5 88.5 20.9756 88.5 45Z" fill="#2F80ED" stroke="white" stroke-width="3"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M24.0352 16.2517L24.0362 16.2536L29.7039 12.0451L24.0352 16.2517ZM33.2408 9.42045L33.3011 9.37578C32.06 1.60484 24.3347 -1.25267 19.4698 0.495649C16.7573 1.47041 14.5183 4.04674 14.868 7.7953C15.6821 8.10795 16.5948 8.56465 17.536 9.16258C16.8441 8.73008 16.1155 8.34044 15.352 7.99818C15.3281 7.98729 15.3024 7.97584 15.2753 7.96409L15.2316 7.94517L15.1892 7.92717C15.0808 7.88136 14.9644 7.83424 14.8737 7.79756L16.0569 19.2312L16.0547 19.2311L14.8688 7.80232C7.55981 4.99833 1.0708 10.1695 0.143126 15.283C-0.374147 18.134 0.798705 21.4164 4.19989 22.986C4.29376 22.9102 4.39099 22.8341 4.49145 22.7579C4.39184 22.8342 4.284 22.92 4.20044 22.9868L14.6263 27.6703L4.20117 22.9903C-1.8667 27.9573 -0.632447 36.2375 3.30481 39.6027C5.49817 41.4773 8.87268 41.9886 11.9222 39.8159C11.9226 39.8182 11.9229 39.8205 11.9233 39.8228C13.1672 47.5898 20.8544 50.7252 25.7183 48.9773C28.4307 48.0025 30.636 45.3177 30.2863 41.5692L30.2842 41.5684L29.1584 30.1621C29.9004 30.1975 30.6449 30.1873 31.3873 30.131C30.6648 30.1875 29.9228 30.2008 29.1659 30.1629L30.2872 41.5648C37.5962 44.3688 44.1041 39.1928 45.0319 34.0793C45.5486 31.2308 44.3566 28.0236 40.9621 26.4526L40.9681 26.4478L40.9768 26.4518C47.0447 21.4848 45.8285 13.2045 41.8912 9.8394C39.6967 7.9637 36.3506 7.2043 33.2998 9.38105L33.2996 9.37947L33.2408 9.42045ZM11.8336 39.0404C11.7841 38.4644 11.762 37.8917 11.7664 37.3237C11.7582 37.9294 11.7823 38.5047 11.8336 39.0404ZM13.5902 29.5318C13.8549 28.9848 14.1464 28.451 14.4634 27.9321C14.1392 28.4578 13.8491 28.9924 13.5902 29.5318ZM22.9946 35.9856C22.3098 35.117 21.691 34.1657 21.1791 33.1383L21.1803 33.1375C21.6982 34.1405 22.3046 35.0942 22.9946 35.9856ZM29.8655 41.3963L29.8961 41.4097L29.9335 41.426C29.4347 41.2141 28.9065 40.9503 28.3627 40.6368C28.8473 40.9107 29.3484 41.1643 29.8655 41.3963ZM40.5527 26.7652C39.4503 27.5638 38.2872 28.2194 37.0853 28.7352C38.4524 28.1575 39.6294 27.4643 40.5527 26.7652ZM33.2643 14.5439L33.2563 14.5952L33.2476 14.6505C33.3387 14.0502 33.4002 13.4419 33.4306 12.8275C33.4058 13.3863 33.3538 13.9605 33.2643 14.5439ZM33.327 9.67726C33.3475 9.88878 33.3664 10.1061 33.383 10.3291C33.3741 10.2272 33.3643 10.1252 33.3537 10.0231C33.3511 9.99919 33.3485 9.97062 33.3459 9.93886L33.3433 9.90513L33.3386 9.8408L33.3318 9.74559L33.327 9.67726ZM23.8401 15.8842C23.3004 14.8945 22.6726 13.955 21.9626 13.08C22.6559 13.925 23.2943 14.8602 23.8401 15.8842ZM15.604 19.2154C14.4546 19.1897 13.3005 19.2735 12.1589 19.469C13.2774 19.2655 14.4367 19.1677 15.604 19.2154ZM5.24286 22.2272C5.18664 22.2647 5.13061 22.3025 5.07471 22.3407C5.03424 22.3684 4.99383 22.3963 4.95349 22.4243C4.88074 22.4749 4.80829 22.5261 4.73602 22.578C4.69299 22.6088 4.65002 22.6399 4.60718 22.6712L4.60419 22.6734C4.80511 22.5245 5.01837 22.3754 5.24286 22.2272Z" transform="translate(21.9045 19.8255)" fill="url(#paint0_radial)"/> -<path opacity="0.65" fill-rule="evenodd" clip-rule="evenodd" d="M16.0295 12.229L14.8425 0.790123C7.53357 -2.01386 1.04458 3.15736 0.116862 8.2708C-0.400378 11.1218 0.772477 14.4042 4.17365 15.9738C6.59782 14.0154 11.2827 11.8803 16.0295 12.229Z" transform="translate(21.9307 26.8173)" fill="url(#paint1_radial)"/> -<path opacity="0.65" fill-rule="evenodd" clip-rule="evenodd" d="M16.1176 12.1553L14.9278 0.782117C7.58484 -2.00893 1.04493 3.18264 0.112904 8.27249C-0.406741 11.1103 0.84878 14.2947 4.26577 15.857C6.697 13.8998 11.129 11.9068 16.1176 12.1553Z" transform="matrix(0.497686 -0.867357 0.864683 0.502317 18 55.3502)" fill="url(#paint2_radial)"/> -<path opacity="0.65" fill-rule="evenodd" clip-rule="evenodd" d="M16.0926 12.1431L14.9659 0.790397C7.62292 -2.00065 1.04732 3.10738 0.115298 8.19723C-0.404347 11.035 0.809439 14.2841 4.22643 15.8464C6.63823 13.9 11.2588 11.8587 16.0926 12.1431Z" transform="matrix(-0.497686 -0.867357 0.864683 -0.502317 40.5922 73)" fill="url(#paint3_radial)"/> -<path opacity="0.65" fill-rule="evenodd" clip-rule="evenodd" d="M15.9815 12.1915L14.8602 0.789647C7.5512 -2.01434 1.04328 3.16169 0.115562 8.27513C-0.401678 11.1261 0.792924 14.3363 4.1941 15.9059C6.54445 13.9641 10.9322 11.9386 15.9815 12.1915Z" transform="translate(67.0519 62.1595) rotate(180)" fill="url(#paint4_radial)"/> -<path opacity="0.65" fill-rule="evenodd" clip-rule="evenodd" d="M16.0794 12.148L14.9513 0.783652C7.60839 -2.0074 1.05938 3.16856 0.127349 8.25841C-0.392295 11.0962 0.632515 14.3774 4.0495 15.9397C5.24452 15.0732 6.74795 14.0894 8.52884 13.3998C10.7325 12.5465 13.2716 12.0251 16.0794 12.148Z" transform="matrix(-0.497686 0.867357 -0.864683 -0.502317 71 33.6823)" fill="url(#paint5_radial)"/> -<path opacity="0.65" fill-rule="evenodd" clip-rule="evenodd" d="M16.0401 12.2374L14.7485 0.773668C7.40559 -2.01738 1.05413 3.26113 0.122106 8.35098C-0.397539 11.1888 0.705495 14.4131 4.12248 15.9754C6.50059 14.048 11.0115 11.9513 16.0401 12.2374Z" transform="matrix(0.497686 0.867357 -0.864683 0.502317 48.5344 16)" fill="url(#paint6_radial)"/> +<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M36.9396 28.0771L36.9407 28.0791L42.6084 23.8706L36.9396 28.0771ZM46.1453 21.2459L46.2056 21.2012C44.9645 13.4303 37.2392 10.5728 32.3743 12.3211C29.6618 13.2959 27.4228 15.8722 27.7725 19.6208C28.5866 19.9334 29.4993 20.3901 30.4405 20.988C29.7485 20.5556 29.02 20.1659 28.2565 19.8237C28.2326 19.8128 28.2068 19.8013 28.1798 19.7896L28.136 19.7706L28.0937 19.7526C27.9853 19.7068 27.8689 19.6597 27.7781 19.623L28.9614 31.0567L28.9592 31.0566L27.7733 19.6278C20.4643 16.8238 13.9753 21.995 13.0476 27.1085C12.5303 29.9594 13.7032 33.2419 17.1044 34.8115C17.1982 34.7357 17.2955 34.6596 17.3959 34.5834C17.2963 34.6596 17.1885 34.7455 17.1049 34.8122L27.5308 39.4958L17.1057 34.8158C11.0378 39.7828 12.272 48.063 16.2093 51.4281C18.4026 53.3028 21.7772 53.8141 24.8267 51.6413C24.8271 51.6437 24.8274 51.646 24.8278 51.6483C26.0717 59.4153 33.7589 62.5507 38.6227 60.8028C41.3351 59.828 43.5405 57.1432 43.1908 53.3947L43.1887 53.3938L42.0629 41.9876C42.8049 42.023 43.5494 42.0128 44.2918 41.9565C43.5693 42.013 42.8273 42.0263 42.0704 41.9884L43.1917 53.3903C50.5007 56.1943 57.0086 51.0182 57.9363 45.9048C58.4531 43.0563 57.261 39.8491 53.8666 38.2781L53.8726 38.2733L53.8813 38.2772C59.9492 33.3103 58.733 25.03 54.7957 21.6649C52.6011 19.7892 49.2551 19.0298 46.2043 21.2065L46.204 21.2049L46.1453 21.2459ZM24.7381 50.8659C24.6885 50.2898 24.6664 49.7171 24.6708 49.1492C24.6627 49.7549 24.6868 50.3301 24.7381 50.8659ZM26.4947 41.3573C26.7594 40.8103 27.0509 40.2765 27.3679 39.7576C27.0437 40.2833 26.7535 40.8179 26.4947 41.3573ZM35.899 47.811C35.2143 46.9425 34.5955 45.9912 34.0836 44.9638L34.0848 44.9629C34.6027 45.966 35.2091 46.9197 35.899 47.811ZM42.77 53.2217L42.8006 53.2352L42.838 53.2515C42.3392 53.0395 41.811 52.7758 41.2672 52.4623C41.7518 52.7362 42.2529 52.9898 42.77 53.2217ZM53.4572 38.5906C52.3548 39.3893 51.1917 40.0449 49.9897 40.5607C51.3569 39.983 52.5339 39.2898 53.4572 38.5906ZM46.1688 26.3694L46.1608 26.4207L46.1521 26.476C46.2432 25.8756 46.3047 25.2674 46.3351 24.6529C46.3103 25.2118 46.2582 25.786 46.1688 26.3694ZM46.2315 21.5027C46.252 21.7142 46.2709 21.9316 46.2875 22.1546C46.2786 22.0527 46.2688 21.9506 46.2582 21.8485C46.2556 21.8247 46.253 21.7961 46.2504 21.7643L46.2478 21.7306L46.243 21.6663L46.2363 21.5711L46.2315 21.5027ZM36.7446 27.7097C36.2049 26.7199 35.5771 25.7805 34.8671 24.9054C35.5604 25.7504 36.1988 26.6857 36.7446 27.7097ZM28.5085 31.0408C27.3591 31.0152 26.205 31.0989 25.0634 31.2945C26.1819 31.0909 27.3412 30.9931 28.5085 31.0408ZM18.1473 34.0527C18.0911 34.0902 18.0351 34.128 17.9792 34.1662C17.9387 34.1939 17.8983 34.2218 17.858 34.2498C17.7852 34.3004 17.7128 34.3516 17.6405 34.4034C17.5975 34.4343 17.5545 34.4654 17.5117 34.4967L17.5087 34.4989C17.7096 34.35 17.9229 34.2008 18.1473 34.0527Z" fill="url(#paint0_radial)"/> +<path opacity="0.65" fill-rule="evenodd" clip-rule="evenodd" d="M28.9603 31.0463L27.7733 19.6074C20.4643 16.8034 13.9753 21.9747 13.0476 27.0881C12.5303 29.939 13.7032 33.2215 17.1044 34.7911C19.5285 32.8327 24.2134 30.6976 28.9603 31.0463Z" fill="url(#paint1_radial)"/> +<path opacity="0.65" fill-rule="evenodd" clip-rule="evenodd" d="M27.532 39.4763L17.1056 34.7954C11.0378 39.7623 12.272 48.0426 16.2093 51.4077C18.4045 53.2839 21.7828 53.7945 24.8343 51.6155C24.3519 48.5236 24.8343 43.6784 27.532 39.4763Z" fill="url(#paint2_radial)"/> +<path opacity="0.65" fill-rule="evenodd" clip-rule="evenodd" d="M34.0831 44.9423L24.8273 51.6222C26.0684 59.3932 33.7578 62.5307 38.6228 60.7824C41.3352 59.8076 43.5405 57.1228 43.1909 53.3743C40.3075 52.2601 36.2428 49.2778 34.0831 44.9423Z" fill="url(#paint3_radial)"/> +<path opacity="0.65" fill-rule="evenodd" clip-rule="evenodd" d="M42.0704 41.968L43.1917 53.3699C50.5007 56.1739 57.0086 50.9978 57.9363 45.8844C58.4536 43.0334 57.259 39.8232 53.8578 38.2536C51.5074 40.1955 47.1197 42.221 42.0704 41.968Z" fill="url(#paint4_radial)"/> +<path opacity="0.65" fill-rule="evenodd" clip-rule="evenodd" d="M43.4933 33.5267L53.8813 38.2568C59.9492 33.2899 58.733 25.0096 54.7957 21.6445C52.6005 19.7683 49.2533 19.0089 46.2018 21.1879C46.3564 22.6597 46.4588 24.4579 46.1687 26.349C45.8099 28.6889 44.997 31.1531 43.4933 33.5267Z" fill="url(#paint5_radial)"/> +<path opacity="0.65" fill-rule="evenodd" clip-rule="evenodd" d="M36.9358 28.0596L46.2056 21.1809C44.9645 13.4099 37.2392 10.5524 32.3742 12.3008C29.6618 13.2755 27.4228 15.8519 27.7725 19.6004C30.6226 20.6949 34.6806 23.5543 36.9358 28.0596Z" fill="url(#paint6_radial)"/> <defs> -<radialGradient id="paint0_radial" cx="0.5" cy="0.5" r="0.5" gradientUnits="userSpaceOnUse" gradientTransform="translate(11.4524 -69.2011) scale(94.3261 103.221) rotate(50.7566)"> -<stop stop-color="#808DFF"/> -<stop offset="0.412807" stop-color="#EDEDED"/> -<stop offset="1" stop-color="#6575FF"/> +<radialGradient id="paint0_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(17.6669 15.2447) rotate(53.2605) scale(49.8783 48.801)"> +<stop stop-color="#F5F5F5"/> +<stop offset="0.412807" stop-color="white"/> +<stop offset="1" stop-color="#DBDBDB"/> </radialGradient> -<radialGradient id="paint1_radial" cx="0.5" cy="0.5" r="0.5" gradientUnits="userSpaceOnUse" gradientTransform="translate(-1.78495 30.4596) scale(21.7507 21.6751) rotate(-102.974)"> +<radialGradient id="paint1_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(19.3019 36.2829) rotate(-103.017) scale(10.8394 10.8734)"> <stop stop-color="white"/> <stop offset="1" stop-color="white" stop-opacity="0.01"/> </radialGradient> -<radialGradient id="paint2_radial" cx="0.5" cy="0.5" r="0.5" gradientUnits="userSpaceOnUse" gradientTransform="translate(-1.79475 30.2369) scale(21.8702 21.5166) rotate(-102.974)"> +<radialGradient id="paint2_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(27.1801 50.5029) rotate(-163.042) scale(10.7545 10.9388)"> <stop stop-color="white"/> <stop offset="1" stop-color="white" stop-opacity="0.01"/> </radialGradient> -<radialGradient id="paint3_radial" cx="0.5" cy="0.5" r="0.5" gradientUnits="userSpaceOnUse" gradientTransform="translate(-1.79197 30.2166) scale(21.8362 21.5022) rotate(-102.974)"> +<radialGradient id="paint3_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(43.3906 50.7488) rotate(136.694) scale(10.7723 10.8964)"> <stop stop-color="white"/> <stop offset="1" stop-color="white" stop-opacity="0.01"/> </radialGradient> -<radialGradient id="paint4_radial" cx="0.5" cy="0.5" r="0.5" gradientUnits="userSpaceOnUse" gradientTransform="translate(-1.7796 30.3301) scale(21.6855 21.5829) rotate(-102.974)"> +<radialGradient id="paint4_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(51.6998 36.7682) rotate(76.9667) scale(10.7941 10.8401)"> <stop stop-color="white"/> <stop offset="1" stop-color="white" stop-opacity="0.01"/> </radialGradient> -<radialGradient id="paint5_radial" cx="0.5" cy="0.5" r="0.5" gradientUnits="userSpaceOnUse" gradientTransform="translate(-1.7905 30.3946) scale(21.8183 21.6288) rotate(-102.974)"> +<radialGradient id="paint5_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(43.7493 22.4711) rotate(17.0544) scale(10.8065 10.917)"> <stop stop-color="white"/> <stop offset="1" stop-color="white" stop-opacity="0.01"/> </radialGradient> -<radialGradient id="paint6_radial" cx="0.5" cy="0.5" r="0.5" gradientUnits="userSpaceOnUse" gradientTransform="translate(-1.78613 30.4626) scale(21.765 21.6772) rotate(-102.974)"> +<radialGradient id="paint6_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(27.6037 22.3039) rotate(-43.1622) scale(10.8536 10.8674)"> <stop stop-color="white"/> <stop offset="1" stop-color="white" stop-opacity="0.01"/> </radialGradient> diff --git a/src/assets/images/desktop_screen_entry.svg b/src/assets/images/desktop_screen_entry.svg index 8e25150d633190c696e3aa4e984227c4d9d3c0e1..2010d8b2f0d7e5ecd69229e08f1102cbd0f31523 100755 --- a/src/assets/images/desktop_screen_entry.svg +++ b/src/assets/images/desktop_screen_entry.svg @@ -1,5 +1,4 @@ -<svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M88.5 45C88.5 69.0244 69.0244 88.5 45 88.5C20.9756 88.5 1.5 69.0244 1.5 45C1.5 20.9756 20.9756 1.5 45 1.5C69.0244 1.5 88.5 20.9756 88.5 45Z" fill="#2F80ED" stroke="white" stroke-width="3"/> -<rect width="20" height="3" transform="translate(35 62)" fill="white"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M3 0C1.34314 0 0 1.34314 0 3V31C0 32.6569 1.34314 34 3 34H38C39.6569 34 41 32.6569 41 31V3C41 1.34314 39.6569 0 38 0H3ZM36 6H5V28H36V6Z" transform="translate(24 28)" fill="white"/> +<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect x="25" y="52" width="20" height="3" fill="white"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M17 18C15.3431 18 14 19.3431 14 21V49C14 50.6569 15.3431 52 17 52H52C53.6569 52 55 50.6569 55 49V21C55 19.3431 53.6569 18 52 18H17ZM50 24H19V46H50V24Z" fill="white"/> </svg> diff --git a/src/assets/images/device_entry.svg b/src/assets/images/device_entry.svg index b0512c0a3e35d0cfce07d07525d17289dea4bf44..3be49f7306ea8946ff28f91c6aa0a2ceb8c77221 100755 --- a/src/assets/images/device_entry.svg +++ b/src/assets/images/device_entry.svg @@ -1,9 +1,9 @@ -<svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M88.5 45C88.5 69.0244 69.0244 88.5 45 88.5C20.9756 88.5 1.5 69.0244 1.5 45C1.5 20.9756 20.9756 1.5 45 1.5C69.0244 1.5 88.5 20.9756 88.5 45Z" fill="#2F80ED" stroke="white" stroke-width="3"/> -<path d="M6.43085 10.78C4.63879 11.0707 0.989932 11.8268 0 12.3286C0.407286 0.547339 6.88406 -1.40044 10.4308 0.779999C6.20865 1.90137 6.32903 8.23614 6.43085 10.78Z" transform="translate(63.9308 26.22) scale(-1 1)" fill="#C4C4C4"/> -<path d="M6.43085 10.78C4.63879 11.0707 0.989932 11.8268 0 12.3286C0.407286 0.547339 6.88406 -1.40044 10.4308 0.779999C6.20865 1.90137 6.32903 8.23614 6.43085 10.78Z" transform="translate(63.9308 26.22) scale(-1 1)" fill="#EFEFEF"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M24.5605 5.80482C25.9101 5.97699 27.2066 5.21939 27.7191 3.95909C28.2562 2.6386 28.7836 1.47353 29.2965 0.449768C30.5023 0.486954 31.5953 0.526215 32.5396 0.576248C37.6468 0.846878 41.0005 2.3575 43.082 4.39674C41.652 5.02917 39.9324 5.87749 38.2806 6.7238C36.5808 7.59465 34.9323 8.47398 33.7101 9.1348C33.0986 9.46544 32.5928 9.74187 32.2397 9.93584C32.063 10.0328 31.9245 10.1093 31.8299 10.1615L31.6837 10.2425C31.1482 10.5401 30.8475 11.1333 30.924 11.741L30.9331 20.2067C30.9927 20.6802 31.274 21.097 31.6908 21.3294C32.1127 21.5701 32.6088 21.5076 33.0902 21.3629L33.2202 21.3031C33.3336 21.2509 33.4994 21.1743 33.71 21.0762C33.9433 20.9677 34.2318 20.8327 34.5654 20.6754C34.8342 20.5487 35.1323 20.4074 35.4543 20.2536L35.6971 20.1377C37.2061 19.4175 39.9301 18.1175 41.7683 17.163C42.9724 16.5377 44.1934 15.8805 45.311 15.2375C44.9258 16.2405 44.4028 17.0819 43.8096 17.6619L26.5396 27.0762C25.1221 26.0333 25.607 25.4727 26.4716 24.473C28.0096 22.6948 30.7492 19.5272 26.1215 9.78375C20.5023 5.35844 7.8877 5.27588 2.50793 5.24068C0.768799 5.22929 -0.214233 5.22285 0.0396118 5.07625C1.75507 4.08553 12.9818 0.917801 16.6978 0C16.611 0.1875 16.5275 0.367569 16.4472 0.539215C16.4064 0.626404 16.3665 0.711426 16.3274 0.794144C15.9169 1.6626 15.947 2.67513 16.4086 3.51761C16.8701 4.36008 17.7072 4.93059 18.66 5.05214L24.5605 5.80482Z" transform="translate(22.9604 35.9238)" fill="white" stroke="white"/> -<path d="M22.1005 13L16.2 13.7527C8.38013 -5.47592 0.509107 1.80677 0 14.3013C0.0509108 6.10544 2.69312 -0.0498744 10.3976 0.000304595C16.5612 0.0404478 19.9736 8.50061 22.1005 13Z" transform="translate(64.1005 25) scale(-1 1)" fill="white"/> -<path d="M3.07742 20.6423C2.4711 19.613 2.00635 17.7557 2.00006 14.4631C2.43733 8.15539 3.498 6.26762 5.0883 5.3118C6.01698 4.75364 7.34242 4.37637 9.41959 4.05502C10.6769 3.86051 12.0676 3.70364 13.6888 3.52076C14.7747 3.39827 15.9639 3.26412 17.2858 3.09918C18.6038 2.93472 19.7807 2.77388 20.8505 2.62767C22.3081 2.42847 23.567 2.25642 24.7135 2.13937C26.7031 1.93625 27.9151 1.95812 28.7146 2.18707C29.3326 2.36405 29.7427 2.66797 30.1138 3.44848C30.5489 4.36362 30.8831 5.86199 31.0919 8.33907C31.0808 14.4601 29.8232 16.4976 28.3144 17.5182C27.4432 18.1075 26.2773 18.5099 24.6232 18.8794C23.8055 19.062 22.9194 19.2267 21.9273 19.4105L21.8974 19.4161C20.9396 19.5935 19.8961 19.7869 18.7925 20.0223C16.8122 20.3393 14.9038 20.7345 13.1884 21.0899C12.3664 21.2601 11.5886 21.4212 10.8685 21.5601C8.517 22.0137 6.76926 22.233 5.45062 22.0716C4.27945 21.9284 3.58934 21.5113 3.07742 20.6423Z" transform="translate(53.092 39.8761) scale(-1 1)" stroke="white" stroke-width="4"/> -<path d="M0 10.3292L0.685179 4.47255C4.92876 3.04264 9.69318 1.20359 12.5676 0.221048C14.5183 -0.445764 15.9365 0.435362 16.0866 2.33438C16.1919 3.66552 15.1923 4.93186 13.973 5.41957C12.7537 5.90728 0 10.3292 0 10.3292Z" transform="translate(55.4919 43.7408) rotate(-6.67284)" fill="white"/> +<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M48.5 26C50.2921 26.2907 53.9409 27.0468 54.9308 27.5486C54.5236 15.7673 48.0468 13.8196 44.5 16C48.7222 17.1214 48.6018 23.4561 48.5 26Z" fill="#C4C4C4"/> +<path d="M48.5 26C50.2921 26.2907 53.9409 27.0468 54.9308 27.5486C54.5236 15.7673 48.0468 13.8196 44.5 16C48.7222 17.1214 48.6018 23.4561 48.5 26Z" fill="#EFEFEF"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M38.5209 30.7286C39.8705 30.9007 41.167 30.1431 41.6795 28.8828C42.2166 27.5623 42.744 26.3973 43.2569 25.3735C44.4626 25.4107 45.5557 25.45 46.5 25.5C51.6072 25.7706 54.9609 27.2812 57.0424 29.3205C55.6124 29.9529 53.8928 30.8012 52.241 31.6476C50.5412 32.5184 48.8927 33.3977 47.6705 34.0585C47.059 34.3892 46.5532 34.6656 46.2001 34.8596C46.0234 34.9566 45.8849 35.033 45.7903 35.0853L45.644 35.1663C45.1086 35.4638 44.8079 36.057 44.8844 36.6648L44.8935 45.1305C44.9531 45.6039 45.2344 46.0208 45.6512 46.2532C46.0731 46.4938 46.5692 46.4314 47.0506 46.2866L47.1806 46.2269C47.294 46.1746 47.4598 46.098 47.6704 46C47.9037 45.8914 48.1922 45.7565 48.5258 45.5992C48.7946 45.4724 49.0927 45.3311 49.4147 45.1773L49.6575 45.0614C51.1664 44.3413 53.8905 43.0412 55.7287 42.0867C56.9328 41.4615 58.1537 40.8042 59.2714 40.1612C58.8862 41.1642 58.3632 42.0057 57.77 42.5856L40.5 52C39.0825 50.957 39.5674 50.3965 40.432 49.3968C41.97 47.6185 44.7096 44.4509 40.0818 34.7075C34.4626 30.2822 21.8481 30.1996 16.4683 30.1644C14.7292 30.153 13.7462 30.1466 14 30C15.7155 29.0093 26.9421 25.8416 30.6582 24.9238C30.5714 25.1113 30.4879 25.2913 30.4076 25.463C30.3668 25.5502 30.3269 25.6352 30.2878 25.7179C29.8773 26.5863 29.9074 27.5989 30.369 28.4414C30.8304 29.2838 31.6675 29.8543 32.6204 29.9759L38.5209 30.7286Z" fill="white"/> +<path d="M41.6795 28.8828L41.2163 28.6945L41.2163 28.6945L41.6795 28.8828ZM38.5209 30.7286L38.5842 30.2326L38.5842 30.2326L38.5209 30.7286ZM43.2569 25.3735L43.2723 24.8738L42.953 24.8639L42.8099 25.1496L43.2569 25.3735ZM46.5 25.5L46.5265 25.0007L46.5265 25.0007L46.5 25.5ZM57.0424 29.3205L57.2447 29.7778L57.9192 29.4795L57.3923 28.9633L57.0424 29.3205ZM52.241 31.6476L52.469 32.0925L52.469 32.0925L52.241 31.6476ZM47.6705 34.0585L47.4327 33.6187L47.4327 33.6187L47.6705 34.0585ZM46.2001 34.8596L46.4407 35.2979L46.4408 35.2978L46.2001 34.8596ZM45.7903 35.0853L45.5485 34.6477L45.548 34.6479L45.7903 35.0853ZM45.644 35.1663L45.4018 34.7289L45.4012 34.7292L45.644 35.1663ZM44.8844 36.6648L45.3844 36.6643L45.3844 36.6332L45.3805 36.6023L44.8844 36.6648ZM44.8935 45.1305L44.3935 45.131L44.3935 45.1621L44.3974 45.193L44.8935 45.1305ZM45.6512 46.2532L45.8989 45.8188L45.8947 45.8165L45.6512 46.2532ZM47.0506 46.2866L47.1945 46.7655L47.2278 46.7555L47.2594 46.7409L47.0506 46.2866ZM47.1806 46.2269L47.3894 46.6812L47.3898 46.681L47.1806 46.2269ZM47.6704 46L47.8814 46.4533L47.8814 46.4533L47.6704 46ZM48.5258 45.5992L48.7391 46.0514L48.7391 46.0514L48.5258 45.5992ZM49.4147 45.1773L49.1994 44.7261L49.1993 44.7261L49.4147 45.1773ZM49.6575 45.0614L49.4422 44.6102L49.4422 44.6102L49.6575 45.0614ZM55.7287 42.0867L55.4983 41.643L55.4983 41.643L55.7287 42.0867ZM59.2714 40.1612L59.7381 40.3405L60.2432 39.0252L59.022 39.7278L59.2714 40.1612ZM57.77 42.5856L58.0093 43.0246L58.0701 42.9915L58.1196 42.9431L57.77 42.5856ZM40.5 52L40.2037 52.4027L40.46 52.5913L40.7393 52.439L40.5 52ZM40.432 49.3968L40.8102 49.7238V49.7238L40.432 49.3968ZM40.0818 34.7075L40.5335 34.493L40.4832 34.3872L40.3912 34.3147L40.0818 34.7075ZM16.4683 30.1644L16.4651 30.6644H16.4651L16.4683 30.1644ZM14 30L13.7499 29.567L13.7499 29.567L14 30ZM30.6582 24.9238L31.1119 25.1338L31.5495 24.1886L30.5383 24.4383L30.6582 24.9238ZM30.4076 25.463L30.8605 25.6748L30.8605 25.6748L30.4076 25.463ZM30.2878 25.7179L29.8358 25.5041L29.8357 25.5042L30.2878 25.7179ZM30.369 28.4414L30.8075 28.2011L30.8075 28.2011L30.369 28.4414ZM32.6204 29.9759L32.6837 29.4799L32.6204 29.9759ZM41.2163 28.6945C40.7892 29.7447 39.7088 30.3761 38.5842 30.2326L38.4577 31.2246C40.0321 31.4254 41.5447 30.5416 42.1427 29.0712L41.2163 28.6945ZM42.8099 25.1496C42.2905 26.1863 41.7577 27.3633 41.2163 28.6945L42.1427 29.0712C42.6754 27.7614 43.1975 26.6083 43.7039 25.5975L42.8099 25.1496ZM46.5265 25.0007C45.5764 24.9504 44.4789 24.911 43.2723 24.8738L43.2415 25.8733C44.4464 25.9104 45.5349 25.9496 46.4735 25.9993L46.5265 25.0007ZM57.3923 28.9633C55.2011 26.8166 51.7191 25.2759 46.5265 25.0007L46.4735 25.9993C51.4953 26.2654 54.7206 27.7459 56.6925 29.6777L57.3923 28.9633ZM52.469 32.0925C54.1179 31.2477 55.8276 30.4045 57.2447 29.7778L56.8402 28.8632C55.3972 29.5014 53.6677 30.3548 52.013 31.2026L52.469 32.0925ZM47.9083 34.4984C49.1286 33.8386 50.7736 32.9611 52.469 32.0925L52.013 31.2026C50.3088 32.0757 48.6568 32.9569 47.4327 33.6187L47.9083 34.4984ZM46.4408 35.2978C46.7931 35.1043 47.2979 34.8284 47.9083 34.4984L47.4327 33.6187C46.8201 33.95 46.3134 34.2269 45.9594 34.4213L46.4408 35.2978ZM46.0321 35.5229C46.1264 35.4708 46.2645 35.3946 46.4407 35.2979L45.9594 34.4213C45.7823 34.5186 45.6434 34.5952 45.5485 34.6477L46.0321 35.5229ZM45.8863 35.6037L46.0325 35.5227L45.548 34.6479L45.4018 34.7289L45.8863 35.6037ZM45.3805 36.6023C45.3295 36.1972 45.5299 35.8017 45.8869 35.6033L45.4012 34.7292C44.6872 35.1259 44.2863 35.9169 44.3883 36.7273L45.3805 36.6023ZM45.3935 45.1299L45.3844 36.6643L44.3844 36.6653L44.3935 45.131L45.3935 45.1299ZM45.8947 45.8165C45.6169 45.6615 45.4293 45.3836 45.3896 45.068L44.3974 45.193C44.4769 45.8242 44.852 46.38 45.4077 46.6898L45.8947 45.8165ZM46.9067 45.8078C46.454 45.9439 46.1332 45.9525 45.8989 45.8188L45.4034 46.6875C46.0129 47.0351 46.6843 46.9188 47.1945 46.7655L46.9067 45.8078ZM46.9718 45.7726L46.8418 45.8323L47.2594 46.7409L47.3894 46.6812L46.9718 45.7726ZM47.4594 45.5467C47.2495 45.6444 47.0843 45.7207 46.9714 45.7728L47.3898 46.681C47.5037 46.6285 47.6701 46.5516 47.8814 46.4533L47.4594 45.5467ZM48.3125 45.1469C47.9798 45.3039 47.692 45.4384 47.4594 45.5467L47.8814 46.4533C48.1153 46.3444 48.4046 46.2091 48.7391 46.0514L48.3125 45.1469ZM49.1993 44.7261C48.878 44.8795 48.5806 45.0205 48.3125 45.1469L48.7391 46.0514C49.0085 45.9243 49.3073 45.7827 49.6302 45.6285L49.1993 44.7261ZM49.4422 44.6102L49.1994 44.7261L49.6301 45.6286L49.8729 45.5127L49.4422 44.6102ZM55.4983 41.643C53.6691 42.5928 50.9535 43.8889 49.4422 44.6102L49.8729 45.5127C51.3793 44.7937 54.1119 43.4897 55.9591 42.5305L55.4983 41.643ZM59.022 39.7278C57.9126 40.3661 56.6984 41.0198 55.4983 41.643L55.9591 42.5305C57.1672 41.9031 58.3949 41.2423 59.5207 40.5946L59.022 39.7278ZM58.1196 42.9431C58.7765 42.3009 59.3341 41.3925 59.7381 40.3405L58.8046 39.982C58.4382 40.9359 57.95 41.7104 57.4205 42.2281L58.1196 42.9431ZM40.7393 52.439L58.0093 43.0246L57.5307 42.1466L40.2607 51.561L40.7393 52.439ZM40.0538 49.0697C39.843 49.3134 39.6308 49.5579 39.4674 49.7941C39.3039 50.0304 39.1557 50.3043 39.1145 50.6168C39.0235 51.3074 39.4737 51.8656 40.2037 52.4027L40.7963 51.5973C40.1089 51.0914 40.0927 50.8479 40.1059 50.7475C40.1169 50.6645 40.1634 50.5457 40.2897 50.3631C40.4161 50.1804 40.5887 49.9799 40.8102 49.7238L40.0538 49.0697ZM39.6302 34.922C41.9222 39.7476 42.3544 42.8735 42.0735 44.9728C41.7952 47.0523 40.8106 48.1947 40.0538 49.0697L40.8102 49.7238C41.5914 48.8206 42.7455 47.4901 43.0647 45.1054C43.3811 42.7404 42.8693 39.4108 40.5335 34.493L39.6302 34.922ZM16.4651 30.6644C19.1564 30.682 23.627 30.7117 28.122 31.2857C32.6411 31.8627 37.0783 32.9786 39.7725 35.1003L40.3912 34.3147C37.4662 32.0111 32.7865 30.8731 28.2486 30.2937C23.6865 29.7112 19.1601 29.682 16.4716 29.6644L16.4651 30.6644ZM13.7499 29.567C13.7145 29.5875 13.6378 29.6347 13.5725 29.7184C13.4953 29.8174 13.4056 30.0157 13.5009 30.2425C13.5736 30.4154 13.7129 30.4913 13.7561 30.5134C13.8154 30.5439 13.8724 30.5607 13.9101 30.5703C14.05 30.6061 14.2529 30.622 14.4621 30.6323C14.907 30.6543 15.6013 30.6588 16.4651 30.6644L16.4716 29.6644C15.5962 29.6587 14.9294 29.6542 14.5115 29.6335C14.4083 29.6284 14.3255 29.6226 14.2617 29.616C14.2299 29.6128 14.2051 29.6096 14.1863 29.6067C14.177 29.6053 14.17 29.604 14.1649 29.603C14.1598 29.602 14.1576 29.6015 14.158 29.6015C14.1581 29.6016 14.1627 29.6028 14.1707 29.6055C14.1781 29.6081 14.1933 29.6138 14.2129 29.6239C14.2319 29.6336 14.356 29.6962 14.4228 29.8549C14.5121 30.0675 14.4256 30.2506 14.3611 30.3334C14.3084 30.4009 14.2538 30.4308 14.2501 30.433L13.7499 29.567ZM30.5383 24.4383C28.6705 24.8997 24.9271 25.9233 21.4371 26.955C19.6917 27.4711 18.0053 27.9904 16.6459 28.4436C15.3085 28.8894 14.2317 29.2888 13.7499 29.567L14.2501 30.433C14.626 30.2159 15.5959 29.8477 16.9621 29.3923C18.3062 28.9442 19.9804 28.4285 21.7206 27.914C25.2019 26.8848 28.9298 25.8657 30.7781 25.4092L30.5383 24.4383ZM30.8605 25.6748C30.9412 25.5022 31.0251 25.3215 31.1119 25.1338L30.2045 24.7137C30.1178 24.901 30.0345 25.0804 29.9547 25.2512L30.8605 25.6748ZM30.7398 25.9317C30.7793 25.8481 30.8195 25.7624 30.8605 25.6748L29.9547 25.2512C29.9141 25.3379 29.8745 25.4223 29.8358 25.5041L30.7398 25.9317ZM30.8075 28.2011C30.4228 27.4991 30.3977 26.6553 30.7398 25.9316L29.8357 25.5042C29.3568 26.5174 29.392 27.6987 29.9305 28.6816L30.8075 28.2011ZM32.6837 29.4799C31.8896 29.3786 31.192 28.9032 30.8075 28.2011L29.9304 28.6816C30.4689 29.6645 31.4455 30.3301 32.5572 30.4719L32.6837 29.4799ZM38.5842 30.2326L32.6837 29.4799L32.5572 30.4719L38.4577 31.2246L38.5842 30.2326Z" fill="white"/> +<path d="M33 27L38.9005 27.7527C46.7204 8.52408 54.5914 15.8068 55.1005 28.3013C55.0496 20.1054 52.4074 13.9501 44.7029 14.0003C38.5393 14.0404 35.1269 22.5006 33 27Z" fill="white"/> +<path d="M41.0146 49.5184C41.6209 48.4891 42.0856 46.6318 42.0919 43.3392C41.6547 37.0315 40.594 35.1437 39.0037 34.1879C38.075 33.6298 36.7496 33.2525 34.6724 32.9311C33.4151 32.7366 32.0244 32.5798 30.4032 32.3969C29.3173 32.2744 28.1281 32.1402 26.8062 31.9753C25.4882 31.8108 24.3113 31.65 23.2415 31.5038C21.7839 31.3046 20.525 31.1325 19.3785 31.0155C17.3889 30.8124 16.1769 30.8342 15.3774 31.0632C14.7593 31.2402 14.3493 31.5441 13.9782 32.3246C13.5431 33.2397 13.2089 34.7381 13.0001 37.2152C13.0112 43.3363 14.2688 45.3737 15.7776 46.3943C16.6488 46.9836 17.8147 47.386 19.4688 47.7555C20.2865 47.9381 21.1726 48.1028 22.1646 48.2866L22.1946 48.2922C23.1523 48.4696 24.1959 48.663 25.2995 48.8984C27.2798 49.2154 29.1882 49.6107 30.9036 49.966C31.7256 50.1363 32.5034 50.2974 33.2235 50.4363C35.575 50.8898 37.3227 51.1091 38.6414 50.9478C39.8125 50.8045 40.5026 50.3874 41.0146 49.5184Z" stroke="white" stroke-width="4"/> +<path d="M47.6921 43L47.6921 37.1034C51.7408 35.1901 56.2593 32.8099 59 31.5C60.86 30.611 62.371 31.3214 62.7408 33.1901C63 34.5 62.1543 35.8739 61 36.5C59.8457 37.1261 47.6921 43 47.6921 43Z" fill="white"/> </svg> diff --git a/src/assets/images/dropdown_arrow.png b/src/assets/images/dropdown_arrow.png index caa42c1ffed82796540acdc192201cf20e822e0b..4f44ce8285283dae98d9d66aaf596f8e0087cc0f 100755 Binary files a/src/assets/images/dropdown_arrow.png and b/src/assets/images/dropdown_arrow.png differ diff --git a/src/assets/images/dropdown_arrow@2x.png b/src/assets/images/dropdown_arrow@2x.png index d4e74eb212652021837a17d860578c6f7114dcd5..f7ccabc58de0786cc8e0574fb1b8713d35ce7d0e 100755 Binary files a/src/assets/images/dropdown_arrow@2x.png and b/src/assets/images/dropdown_arrow@2x.png differ diff --git a/src/assets/images/generic_vr_entry.svg b/src/assets/images/generic_vr_entry.svg index a47a2e8fd8aec7ececd8742b6a061991c9f8fcc4..3f9f733d866b403ff347a1fcecbfb071c734a0cb 100755 --- a/src/assets/images/generic_vr_entry.svg +++ b/src/assets/images/generic_vr_entry.svg @@ -1,4 +1,3 @@ -<svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M88.5 45C88.5 69.0244 69.0244 88.5 45 88.5C20.9756 88.5 1.5 69.0244 1.5 45C1.5 20.9756 20.9756 1.5 45 1.5C69.0244 1.5 88.5 20.9756 88.5 45Z" fill="#2F80ED" stroke="white" stroke-width="3"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M42.1704 0H2.71227C1.24057 0 0 1.22604 0 2.73805V25.2622C0 26.7742 1.24057 28 2.77108 28H13.5496C14.7053 28 15.6958 27.3015 16.112 26.3081L19.2462 18.8289C19.7747 17.5677 21.0324 16.6805 22.5 16.6805C23.9676 16.6805 25.2253 17.5677 25.7538 18.8289L28.888 26.3081C29.3042 27.3015 30.2947 28 31.3916 28H42.1704C43.7594 28 45 26.7742 45 25.2622V2.73805C45 1.22604 43.7594 0 42.1704 0ZM12.294 18.9274C9.53693 18.9274 7.30457 16.7226 7.30457 14C7.30457 11.2774 9.53693 9.07287 12.294 9.07287C15.0507 9.07287 17.2785 11.2774 17.2785 14C17.2785 16.7226 15.0459 18.9274 12.294 18.9274ZM32.7086 18.9251C29.9531 18.9251 27.7215 16.7214 27.7215 14C27.7215 11.2789 29.9531 9.07514 32.7086 9.07514C35.4641 9.07514 37.6957 11.2789 37.6957 14C37.6957 16.7214 35.4641 18.9251 32.7086 18.9251Z" transform="translate(22 32)" fill="white"/> +<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M56.1704 22H16.7123C15.2406 22 14 23.226 14 24.7381V47.2622C14 48.7742 15.2406 50 16.7711 50H27.5496C28.7053 50 29.6958 49.3015 30.112 48.3081L33.2462 40.8289C33.7747 39.5677 35.0324 38.6805 36.5 38.6805C37.9676 38.6805 39.2253 39.5677 39.7538 40.8289L42.888 48.3081C43.3042 49.3015 44.2947 50 45.3916 50H56.1704C57.7594 50 59 48.7742 59 47.2622V24.7381C59 23.226 57.7594 22 56.1704 22ZM26.294 40.9274C23.5369 40.9274 21.3046 38.7226 21.3046 36C21.3046 33.2774 23.5369 31.0729 26.294 31.0729C29.0507 31.0729 31.2785 33.2774 31.2785 36C31.2785 38.7226 29.0459 40.9274 26.294 40.9274ZM46.7086 40.9251C43.9531 40.9251 41.7215 38.7214 41.7215 36C41.7215 33.2789 43.9531 31.0751 46.7086 31.0751C49.4641 31.0751 51.6957 33.2789 51.6957 36C51.6957 38.7214 49.4641 40.9251 46.7086 40.9251Z" fill="white"/> </svg> diff --git a/src/assets/images/hub-preview-light-no-shadow.png b/src/assets/images/hub-preview-light-no-shadow.png new file mode 100755 index 0000000000000000000000000000000000000000..c06da7bb0cb7084799ff0832ca3246c8589144fe Binary files /dev/null and b/src/assets/images/hub-preview-light-no-shadow.png differ diff --git a/src/assets/images/hub-preview-white.png b/src/assets/images/hub-preview-white.png index dd83924b037ec54eafc7ca63a14557ed4b44c473..a2ce192fa9022afdd85511302595798a49b178ad 100755 Binary files a/src/assets/images/hub-preview-white.png and b/src/assets/images/hub-preview-white.png differ diff --git a/src/assets/images/link_dialog_header.svg b/src/assets/images/link_dialog_header.svg new file mode 100755 index 0000000000000000000000000000000000000000..773243545d048ed0a1920d66d085d3df1e8f4bca --- /dev/null +++ b/src/assets/images/link_dialog_header.svg @@ -0,0 +1,3 @@ +<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M56.1704 22H16.7123C15.2406 22 14 23.226 14 24.7381V47.2622C14 48.7742 15.2406 50 16.7711 50H27.5496C28.7053 50 29.6958 49.3015 30.112 48.3081L33.2462 40.8289C33.7747 39.5677 35.0324 38.6805 36.5 38.6805C37.9676 38.6805 39.2253 39.5677 39.7538 40.8289L42.888 48.3081C43.3042 49.3015 44.2947 50 45.3916 50H56.1704C57.7594 50 59 48.7742 59 47.2622V24.7381C59 23.226 57.7594 22 56.1704 22ZM26.294 40.9274C23.5369 40.9274 21.3046 38.7226 21.3046 36C21.3046 33.2774 23.5369 31.0729 26.294 31.0729C29.0507 31.0729 31.2785 33.2774 31.2785 36C31.2785 38.7226 29.0459 40.9274 26.294 40.9274ZM46.7086 40.9251C43.9531 40.9251 41.7215 38.7214 41.7215 36C41.7215 33.2789 43.9531 31.0751 46.7086 31.0751C49.4641 31.0751 51.6957 33.2789 51.6957 36C51.6957 38.7214 49.4641 40.9251 46.7086 40.9251Z" fill="black"/> +</svg> diff --git a/src/assets/images/mic_small.png b/src/assets/images/mic_small.png index cc41c2bb0d36ace15751bffae7d342369da2b0b3..7d04c84c5c5f3f9a6ecd21a370f0ac48a03ed1ca 100755 Binary files a/src/assets/images/mic_small.png and b/src/assets/images/mic_small.png differ diff --git a/src/assets/images/mic_small@2x.png b/src/assets/images/mic_small@2x.png old mode 100644 new mode 100755 index cc41c2bb0d36ace15751bffae7d342369da2b0b3..524062c02acacdda693a4472963364714f3c8617 Binary files a/src/assets/images/mic_small@2x.png and b/src/assets/images/mic_small@2x.png differ diff --git a/src/assets/images/mobile_screen_entry.svg b/src/assets/images/mobile_screen_entry.svg index 5c5b77cda1c2796d18090b29052e3981631131e6..2e602ba7431697de855c50ea844b8159c06b2f18 100755 --- a/src/assets/images/mobile_screen_entry.svg +++ b/src/assets/images/mobile_screen_entry.svg @@ -1,8 +1,6 @@ -<svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M88.5 45C88.5 69.0244 69.0244 88.5 45 88.5C20.9756 88.5 1.5 69.0244 1.5 45C1.5 20.9756 20.9756 1.5 45 1.5C69.0244 1.5 88.5 20.9756 88.5 45Z" fill="#2F80ED" stroke="white" stroke-width="3"/> -<mask id="path-2-inside-1" fill="white"> -<path fill-rule="evenodd" clip-rule="evenodd" d="M5 0C2.23859 0 0 2.23859 0 5V33C0 35.7614 2.23859 38 5 38H20C22.7614 38 25 35.7614 25 33V5C25 2.23859 22.7614 0 20 0H5ZM22 4H3V29H22V4Z"/> +<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg"> +<mask id="path-1-inside-1" fill="white"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M28 17C25.2386 17 23 19.2386 23 22V50C23 52.7614 25.2386 55 28 55H43C45.7614 55 48 52.7614 48 50V22C48 19.2386 45.7614 17 43 17H28ZM45 21H26V46H45V21Z"/> </mask> -<path fill-rule="evenodd" clip-rule="evenodd" d="M5 0C2.23859 0 0 2.23859 0 5V33C0 35.7614 2.23859 38 5 38H20C22.7614 38 25 35.7614 25 33V5C25 2.23859 22.7614 0 20 0H5ZM22 4H3V29H22V4Z" transform="translate(32 26)" fill="#2F80ED"/> -<path d="M3 4V1H0V4H3ZM22 4H25V1H22V4ZM3 29H0V32H3V29ZM22 29V32H25V29H22ZM3 5C3 3.89544 3.89544 3 5 3V-3C0.581732 -3 -3 0.581732 -3 5H3ZM3 33V5H-3V33H3ZM5 35C3.89544 35 3 34.1046 3 33H-3C-3 37.4183 0.581732 41 5 41V35ZM20 35H5V41H20V35ZM22 33C22 34.1046 21.1046 35 20 35V41C24.4183 41 28 37.4183 28 33H22ZM22 5V33H28V5H22ZM20 3C21.1046 3 22 3.89544 22 5H28C28 0.581732 24.4183 -3 20 -3V3ZM5 3H20V-3H5V3ZM3 7H22V1H3V7ZM6 29V4H0V29H6ZM22 26H3V32H22V26ZM19 4V29H25V4H19Z" transform="translate(32 26)" fill="white" mask="url(#path-2-inside-1)"/> +<path d="M26 21V18H23V21H26ZM45 21H48V18H45V21ZM26 46H23V49H26V46ZM45 46V49H48V46H45ZM26 22C26 20.8954 26.8954 20 28 20V14C23.5817 14 20 17.5817 20 22H26ZM26 50V22H20V50H26ZM28 52C26.8954 52 26 51.1046 26 50H20C20 54.4183 23.5817 58 28 58V52ZM43 52H28V58H43V52ZM45 50C45 51.1046 44.1046 52 43 52V58C47.4183 58 51 54.4183 51 50H45ZM45 22V50H51V22H45ZM43 20C44.1046 20 45 20.8954 45 22H51C51 17.5817 47.4183 14 43 14V20ZM28 20H43V14H28V20ZM26 24H45V18H26V24ZM29 46V21H23V46H29ZM45 43H26V49H45V43ZM42 21V46H48V21H42Z" fill="white" mask="url(#path-1-inside-1)"/> </svg> diff --git a/src/assets/images/moz-logo-black.png b/src/assets/images/moz-logo-black.png new file mode 100755 index 0000000000000000000000000000000000000000..f6d2ae4a012a798acc5030c9d462c2bf20366c38 Binary files /dev/null and b/src/assets/images/moz-logo-black.png differ diff --git a/src/assets/stylesheets/audio.scss b/src/assets/stylesheets/audio.scss index c12b548a2ce6f89e379b6bd3d8301bbbc3f47422..7b784427543ab8e9baab0179930aef73474890f1 100644 --- a/src/assets/stylesheets/audio.scss +++ b/src/assets/stylesheets/audio.scss @@ -5,6 +5,7 @@ flex-direction: column; height: 100%; width: 100%; + margin: 24px; &__enter-button-container { flex: 1; @@ -14,6 +15,7 @@ } &__enter-button { @extend %bottom-action-button; + @extend %wide-button; } &__title { @@ -41,14 +43,16 @@ @extend %glass-text; appearance: none; - background-color: rgba(64, 64, 64, 0.2); + background-color: white; -moz-appearance: none; -webkit-appearance: none; + border: 1px solid #e2e2e2; padding: 6px; + font-weight: bold; + font-size: 0.9em; padding-left: 15px; padding-right: 30px; color: white; - font-size: 1.1em; width: 90%; height: 50px; } @@ -110,6 +114,7 @@ justify-content: flex-start; align-items: center; overflow-y: auto; + margin: 24px; &__grant-container { flex: 1; @@ -153,6 +158,7 @@ &__next { @extend %bottom-button; + @extend %wide-button; flex: 1 1; } diff --git a/src/assets/stylesheets/avatar-selector.scss b/src/assets/stylesheets/avatar-selector.scss index 6428a7a68429149616380ac400c1dc48aa5913a2..7257167b83a75fdb4981e821f56fb108b097101c 100644 --- a/src/assets/stylesheets/avatar-selector.scss +++ b/src/assets/stylesheets/avatar-selector.scss @@ -6,6 +6,7 @@ canvas { border-radius: 8px; + background-color: #aaa; } .avatar-selector { diff --git a/src/assets/stylesheets/create-object-dialog.scss b/src/assets/stylesheets/create-object-dialog.scss index 4f612a082874606f1eac993267462b999beadf86..be18f09ec6532f66347e3302bff2e2aded285a8b 100644 --- a/src/assets/stylesheets/create-object-dialog.scss +++ b/src/assets/stylesheets/create-object-dialog.scss @@ -35,36 +35,40 @@ } :local(.cancel-icon) { - color: white; + color: $darkest-grey; + cursor: pointer; + &:hover { color: #FF3D7F } } :local(.upload-icon) { - color: white; + color: $action-color; + cursor: pointer; + &:hover { - color: #2F80ED; + color: $action-color-light; } } :local(.input-border) { display: flex; - border: 0.25em solid white; - border-radius: 1em; + @extend %rounded-border; + @extend %default-font; margin: 1em; padding: 0.5em 0.75em; width: 100%; box-sizing: border-box; - @extend %default-font; } :local(.left-side-of-input) { + @extend %default-font; flex-grow: 1; border: none; white-space: nowrap; background: transparent; - color: white; + color: black; font-size: 1.2em; align-self: center; overflow: hidden; diff --git a/src/assets/stylesheets/entry.scss b/src/assets/stylesheets/entry.scss index 5dc16221c4584917a1ae14997f608bb048073bab..5680a01eda338c063eb8a6550d6c473549ff0329 100644 --- a/src/assets/stylesheets/entry.scss +++ b/src/assets/stylesheets/entry.scss @@ -3,33 +3,58 @@ :local(.entry-dialog) { display: flex; flex-direction: column; - height: 100%; align-items: center; + height: 100%; + position: relative; + + :local(.collapse) { + @extend %fa-icon-button; + color: black; + position: absolute; + top: 0px; + right: 12px; + width: 32px; + height: 32px; + } + + :local(.expand) { + @extend %fa-icon-button; + color: white; + position: absolute; + top: -48px; + right: 12px; + width: 32px; + height: 32px; + } } :local(.entry-button) { + @extend %action-button; + width: 272px; display: flex; - margin: 20px 0; - margin-bottom: 0; + text-align: left; + flex-direction: row; cursor: pointer; - background: none; color: white; - border: none; align-items: center; @extend %default-font; + border: 0; + border-radius: 12px; + margin: 6px; + padding: 12px 18px; + height: auto; :local(.icon) { - @extend %glass-icon; - flex: 1 1 90px; - min-width: 90px; - min-height: 90px; + height: 40px; + width: 40px; } :local(.label) { @extend %glass-text; + color: white; flex: 10 1 auto; margin-left: 20px; - font-size: 1.5em; + font-size: 1.0em; display: flex; flex-direction: column; justify-content: center; @@ -40,7 +65,8 @@ } :local(.subtitle) { - font-size: 0.7em; + font-size: 0.8em; + font-weight: normal; color: $light-text; } } @@ -50,31 +76,58 @@ display: flex; flex-direction: column; flex: 1; + text-align: center; + margin: 24px; + min-height: 150px; + height: 100%; + + :local(.title) { + @extend %top-title; + @extend %glass-text; + margin-bottom: 12px; + margin-right: 8px; + margin-left: 8px; + } + + :local(.center) { + @extend %glass-text; + flex: 10; + } + + :local(.profile-name) { + margin-top: 4px; + margin-bottom: 16px; + @extend %default-font; + font-size: 1.1em; + color: $action-color; + cursor: pointer; + font-weight: bold; + display: flex; + align-items: center; + justify-content: center; + } + + :local(.profile-icon) { + cursor: pointer; + width: 16px; + height: 16px; + margin-right: 12px; + } :local(.button-container) { - margin: auto; - margin-top: -72px; text-align: center; - flex: 10; display: flex; flex-direction: column; - min-height: max-content; - justify-content: center; + height: 100%; + justify-content: flex-end; + align-items: center; - :local(.invite-button) { + :local(.action-button) { @extend %action-button; - align-self: center; - width: 75%; - margin-top: 28px; } - :local(.presence-info) { - margin: 18px; - font-size: 1.3em; - - :local(.people) { - font-weight: bold; - } + :local(.wide-button) { + @extend %wide-button; } } @@ -108,6 +161,6 @@ text-align: center; margin-top: 10px; cursor: pointer; - color: $grey-text; + color: $dark-grey-text; } } diff --git a/src/assets/stylesheets/hub-create.scss b/src/assets/stylesheets/hub-create.scss index ebead263f24b472f915effd1887f6b4db8460ed8..bc9145df6e1246e5a34e60f29efc7fffe7297414 100644 --- a/src/assets/stylesheets/hub-create.scss +++ b/src/assets/stylesheets/hub-create.scss @@ -1,5 +1,20 @@ @import 'shared'; +:local(.placeholder) { + width: 690px; + height: 460px; + + @media (max-width: 768px) , (max-height: 715px) { + width: 525px; + height: 350px; + } + + @media (max-width: 520px) { + width: 90%; + height: 300px; + } +} + :local(.create-panel) { display: flex; flex-direction: column; @@ -50,13 +65,11 @@ font-size: 1.5em; width: 100%; text-align: center; - margin-top: 275px; } @media (max-width: 520px) { padding-left: 15px; font-size: 1.2em; - margin-top: 165px; } } @@ -81,7 +94,6 @@ width: 700px; height: 100%; box-sizing: border-box; - border: 3px solid white; border-radius: 14px; overflow: hidden; pointer-events: none; @@ -101,6 +113,7 @@ height: 100%; object-fit: cover; position: absolute; + filter: contrast(0.9) brightness(1.1); } :local(.labels) { @@ -109,8 +122,7 @@ height: 100%; top: 0; left: 0; - background: rgb(2,0,36); - background: linear-gradient(0deg, rgba(2,0,36,0.324) 0%, rgba(1,0,11,0.1189076314119398) 60%, rgba(0,0,0,0.3242297602634804) 100%); + background: linear-gradient(0deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.0) 50%, rgba(0,0,0,0.4) 100%); :local(.custom-button) { @extend %default-font; @@ -200,30 +212,6 @@ } } } - - :local(.link-code) { - position: absolute; - - bottom: -36px; - - @media (max-height: 715px) { - bottom: -28px; - } - - width: 100%; - text-shadow: 0px 0px 4px rgba(0, 0, 0, 1.0); - text-align: center; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - - :local(.link) { - color: white; - font-size: 1.2em; - text-decoration-color: $light-grey; - } - } } } diff --git a/src/assets/stylesheets/hub.scss b/src/assets/stylesheets/hub.scss index e24bf7f06dd6c27231971ce03e244ac829087d7c..8ce382b26542abc5df4cc138c0846035c700f81a 100644 --- a/src/assets/stylesheets/hub.scss +++ b/src/assets/stylesheets/hub.scss @@ -13,11 +13,14 @@ display: none; } +.grab-cursor { + cursor: grab; +} + .no-cursor { cursor: none; } - .webxr-realities, .webxr-sessions { @extend %unselectable } diff --git a/src/assets/stylesheets/index.scss b/src/assets/stylesheets/index.scss index ed742c0a9b1f707031b5cdb417ca727dcd4a3b5a..904a2075e75f24eb57beb61b5ccea431617bce4f 100644 --- a/src/assets/stylesheets/index.scss +++ b/src/assets/stylesheets/index.scss @@ -11,8 +11,8 @@ $header-section-width: 350px; body { margin: 0; padding: 0; - background-color: black; - color: white; + background-color: white; + color: black; } .home-root { @@ -47,20 +47,22 @@ body { position: fixed; top: 0; left: 0; - opacity: 0.5; + opacity: 0.3; min-width: 100%; - filter: saturate(1.5); min-height: 100%; z-index: 1; } :local(.header-content) { padding: 0.5em 1.75em 0.5em 1.75em; - background-color: $darkest-transparent; + background-color: white; min-height: 65px; height: 65px; display: flex; - border-bottom: 1px solid $darkest-grey; + + @media (max-width: 768px) { + display: none; + } :local(.title-and-nav) { display: flex; @@ -97,12 +99,16 @@ body { align-items: center; a { - margin: 0px 16px 0px 0px; - color: white; + margin: 0px 32px 0px 0px; + color: black; font-weight: bold; font-size: 1.4em; text-decoration: none; } + + a:first-child { + margin-left: 16px; + } } } @@ -118,123 +124,123 @@ body { min-height: 740px; display: flex; flex-direction: column; + justify-content: center; position: relative; @media (max-width: 768px) { - padding: 1em 1.5em 1em 1.5em; - justify-content: space-around; + padding: 0px; min-height: 490px; } :local(.attribution) { position: absolute; right: 12px; - bottom: 12px; - color: $grey-text; - text-shadow: 0px 0px 2px rgba(32, 32, 32, 1.0); - opacity: 0.5; + top: 12px; + color: white; + opacity: 0.8; a { - color: $grey-text; + font-weight: bold; + color: white; + } + + @media (max-width: 768px) { + display: none; } } :local(.container) { - padding-top: 2vw; - padding-left: 2.1em; - padding-right: 2.1em; - flex: 2; - text-shadow: 0px 0px 2px rgba(32, 32, 32, 1.0); + display: flex; + flex-direction: column; + align-items: center; - @media (max-height: 720px) { - padding-bottom: 0px; - flex: 1; + :local(.logo) { + width: 350px; } + padding: 2em; + :local(.title) { - font-size: 4vw; + text-align: center; + font-size: 1.5em; font-weight: bold; - - @media (max-width: 768px) , (max-height: 715px) { - font-size: 1.9em; - } - - @media (max-width: 768px) { - text-align: center; - } - - @media (min-width: 1824px) { - font-size: 4.5em; - } - } - - :local(.subtitle) { - font-size: 2.5vw; - font-weight: lighter; - color: $light-text; - - @media (max-width: 768px) , (max-height: 715px) { - font-size: 1.1em; - margin-top: 0.2em; - } - - @media (max-width: 768px) { - text-align: center; - } - - @media (min-width: 1824px) { - font-size: 2.8em; - } - - @media (max-height: 720px) { - display: none; - } } } :local(.create) { padding: 2.1em; padding-bottom: 3.5vw; - flex: 4; position: relative; @media (max-width: 768px) { padding: 0.5em; } } + + :local(.join-button) { + display: flex; + justify-content: center; + + a { + margin-top: 8px; + @extend %action-button; + } + } } :local(.footer-content) { - padding: 1em 2.25em 1em 2.25em; - background-color: $darkest-transparent; + padding: 1em 2.25em; + margin: 24px; + + @media (max-width: 768px) { + padding: 1em; + margin: 0; + } + min-height: 80px; display: flex; - border-top: 2px solid #242424; align-items: center; - justify-content: center; + justify-content: flex-end; :local(.links) { text-align: center; - color: $dark-grey; + color: black; display: flex; flex-direction: column; + :local(.moz-logo) { + width: 172px; + height: 49px; + margin-left: 18px; + + @media (max-width: 768px) { + width: 113px; + height: 32px; + margin: 0; + } + } + :local(.top) { display: flex; justify-content: space-between; + align-items: flex-end; } :local(.link) { - color: $grey-text; - margin-left: 8px; - margin-right: 8px; + color: black; + font-weight: bold; + margin: 0 18px; + + @media (max-width: 768px) { + display: none; + } } :local(.bottom) { margin-top: 8px; a { - color: $grey-text; + color: black; } } } diff --git a/src/assets/stylesheets/info-dialog.scss b/src/assets/stylesheets/info-dialog.scss index 27ea17a7683c90b7d522bb77f3e635f7fadec0da..447500bef3c1d3372a036ce4ededde2b185c3103 100644 --- a/src/assets/stylesheets/info-dialog.scss +++ b/src/assets/stylesheets/info-dialog.scss @@ -4,7 +4,7 @@ top: 0; left: 0; position: fixed; - color: white; + color: black; z-index: 2; } @@ -14,7 +14,7 @@ grid-template-rows: 1fr 20px minmax(200px, max-content) 20px 1fr; width: 100%; height: 100%; - background-color: rgba(0,0,0,.2); + background-color: rgba(0,0,0,.85); &__box { grid-column: 3; @@ -24,13 +24,13 @@ &__contents { padding: 30px; - background-color: rgba(0,0,0,0.9); box-shadow: 0px 0px 30px 1px #202020; - border-radius: 8px; + border-radius: 12px; display: flex; flex-direction: column; text-align: center; position: relative; + background-color: white; &__title { @extend %top-title; @@ -38,15 +38,16 @@ } &__body { - font-size: 1.1em; - color: $grey-text; + font-size: 0.9em; + font-weight: bold; + color: black; display: flex; flex-direction: column; height: 100%; overflow-y: auto; margin: 10px 0; - a { color: white } + a { color: black } } &__links { @@ -55,17 +56,18 @@ margin: 16px 0; a { margin: 0 12px; - color: $light-text; + color: black; } } &__close { position: absolute; - left: 12px; + right: 12px; top: 6px; - color: white; - font-size: 1.4em; + color: black; + font-size: 1.8em; + font-weight: bold; background: none; cursor: pointer; @@ -100,7 +102,7 @@ &__link_field { @extend %rounded-border; @extend %default-font; - color: $light-text; + color: black; font-size: 1.2em; background-color: transparent; line-height: 2.0em; @@ -143,10 +145,10 @@ &__email_field { @extend %rounded-border; @extend %default-font; - color: $light-text; + color: black; font-size: 1.2em; background-color: transparent; - line-height: 2.0em; + line-height: 2.5em; padding-left: 1.25em; padding-right: 1.25em; margin: 0.5em 0; diff --git a/src/assets/stylesheets/invite-dialog.scss b/src/assets/stylesheets/invite-dialog.scss new file mode 100644 index 0000000000000000000000000000000000000000..cdf5ca880a848a321ea5587d9007d7a2df464767 --- /dev/null +++ b/src/assets/stylesheets/invite-dialog.scss @@ -0,0 +1,105 @@ +@import 'shared.scss'; + +:local(.dialog) { + background-color: $action-color-transparent; + border-radius: 12px; + box-shadow: 0px 5px 30px 1px #333; + display: flex; + flex-direction: column; + align-items: center; + margin-top: 10px; + padding: 14px; + text-align: center; + position: relative; + font-size: 1.0em; + color: white; + z-index: 3; + + a { + color: white; + text-decoration: underline; + font-weight: bold; + } + + :local(.link-button) { + @extend %action-button-selected; + margin-top: 4px; + + @media (max-height: 370px) { + display: none; + } + } +} + +:local(.close) { + position: absolute; + width: 30px; + height: 30px; + right: 12px; + font-size: 2.0em; + top: 0px; + cursor: pointer; +} + +:local(.attach-point) { + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-bottom: 5px solid $action-color; + position: absolute; + top: -5px; +} + +:local(.code) { + color: black; + font-weight: bold; + text-decoration: none; + font-size: 2.0em; + display: flex; + margin: 12px; +} + +:local(.keep-open) { + font-size: 0.8em; +} + +:local(.domain) { + input { + @extend %default-font; + font-weight: bold; + text-decoration: none; + color: black; + text-align: center; + background-color: white; + border: 1px solid #e2e2e2; + border-radius: 12px; + margin: 12px; + font-size: 1.8em; + padding: 14px; + display: block; + width: 295px; + } +} + + +:local(.digit) { + padding: 0 8px; + margin: 2px; + background-color: white; + border: 1px solid #e2e2e2; + border-radius: 12px; + width: 32px; + height: 64px; + display: flex; + align-items: center; + justify-content: center; +} + +:local(.code-loading-panel) { + background: none; +} + +:local(.hub-link-link) { + font-size: 1.2em; +} diff --git a/src/assets/stylesheets/link-dialog.scss b/src/assets/stylesheets/link-dialog.scss index aafcac281e33a362fc69871f9474fff32ddd73b7..d137a31fb2fffea16af362eb4a6adcb999081d4e 100644 --- a/src/assets/stylesheets/link-dialog.scss +++ b/src/assets/stylesheets/link-dialog.scss @@ -1,19 +1,80 @@ +@import 'shared.scss'; + +:local(.dialog) { + position: absolute; + color: white; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.9); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 10; + pointer-events: auto; +} + +:local(.header) { + font-size: 2em; + font-weight: bold; + margin-bottom: 12px; + text-align: center; + + @media(max-height: 420px) { + display: none; + } +} + +:local(.contents) { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + :local(.domain) , :local(.code) { color: white; - font-family: monospace; font-weight: bold; text-decoration: none; } :local(.domain) { + @extend %default-font; font-size: 3em; + font-weight: bold; + text-decoration: none; + color: black; + text-align: center; + background-color: white; + border: 1px solid #e2e2e2; + border-radius: 12px; + margin: 12px; padding: 14px; display: block; + width: 295px; } :local(.code) { - font-size: 4.0em; - padding: 8px; + color: black; + font-weight: bold; + text-decoration: none; + font-size: 2.5em; + display: flex; + margin: 12px; +} + +:local(.imageHeader) { + width: 100px; + height: 100px; + background-color: white; + border-radius: 50px; + fill: black; + margin-bottom: 24px; + padding-right: 4px; + + @media(max-height: 420px) { + display: none; + } } :local(.keep-open) { @@ -22,8 +83,34 @@ :local(.digit) { padding: 0 8px; + margin: 2px; + background-color: white; + border: 1px solid #e2e2e2; + border-radius: 12px; + width: 32px; + height: 64px; + display: flex; + align-items: center; + justify-content: center; } :local(.code-loading-panel) { background: none; } + +:local(.close) { + position: absolute; + width: 30px; + height: 30px; + right: 12px; + font-size: 3.0em; + top: 0px; + cursor: pointer; +} + +:local(.close-button) { + @extend %action-button; + background-color: $darker-grey; + margin-top: 24px; +} + diff --git a/src/assets/stylesheets/link.scss b/src/assets/stylesheets/link.scss index b94325c5039062d7848a4d254df9d27b88a7ceb4..ee36c30f0086562c06388b9e13bb2f8e4dbf9699 100644 --- a/src/assets/stylesheets/link.scss +++ b/src/assets/stylesheets/link.scss @@ -8,12 +8,23 @@ body { margin: 0; padding: 0; - background-color: black; - color: white; + background-color: white; + color: black; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + overflow-y: hidden; +} + +button { + outline: none; + -webkit-tap-highlight-color: rgba(255, 255, 255, 0); + -webkit-tap-highlight-color: transparent; } a { - color: white; + color: black; } .link-root { @@ -27,6 +38,29 @@ a { position: absolute; } +:local(.logo) { + position: absolute; + top: 0; + left: 0; + width: 100%; + display: flex; + justify-content: center; + margin-top: 24px; + + img { + width: 247px; + height: 57px; + } + + @media(max-height: 600px) { + display: none; + } + + @media(max-width: 690px) { + display: none; + } +} + :local(.link) { display: flex; align-items: center; @@ -38,9 +72,9 @@ a { display: flex; flex-direction: row; align-items: center; - justify-content: center; - color: $grey-text; - font-size: 1.4em; + color: black; + font-weight: bold; + font-size: 1.2em; @media (max-width: 690px) { flex-direction: column; @@ -74,6 +108,7 @@ a { :local(.entry-footer-image) { width: 200px; margin: 12px; + margin-top: 24px; } @@ -87,7 +122,8 @@ a { } :local(.header) { - margin: 16px; + text-align: center; + white-space: pre-wrap; } :local(.footer) { @@ -109,36 +145,44 @@ a { grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 1fr 1fr 1fr 1fr; text-align: center; + background-color: #f7f7f7; + border-radius: 24px; + padding: 12px; } :local(.keypad-button) { @extend %big-icon-button; - font-size: 1.8em; - font-family: sans-serif; - border: 4px $light-grey solid; + @extend %default-font; + font-weight: bold; + color: $action-color; + font-size: 1.4em; border-radius: 128px; + box-shadow: 0px 2px 12px #ccc; min-width: 80px; min-height: 80px; cursor: pointer; line-height: 68px; margin: 8px; + background: white; } :local(.keypad-button):active { - background-color: $darker-grey; -} - -:local(.keypad-zero-button) { - grid-column: 2; + background-color: $light-grey; } :local(.keypad-button):disabled { color: $light-grey; - border: 6px $dark-grey solid; } -:local(.keypad-backspace) , :local(.keypad-backspace):disabled , :local(.keypad-backspace):active { +:local(.keypad-backspace) , :local(.keypad-backspace):disabled , :local(.keypad-backspace):active, :local(.keypad-toggle-mode), :local(.keypad-toggle-mode):disabled, :local(.keypad-toggle-mode):active { border: none; + background: transparent; + box-shadow: none; + color: black; +} + +:local(.keypad-backspace) { + grid-column: 3; } :local(.keypad-backspace):active { @@ -146,37 +190,66 @@ a { color: $light-grey; } -:local(.entered-digits) { - font-face: monospace; +:local(.keypad-toggle-mode) { + font-size: 1.0em; +} + +:local(.entered) { + @extend %default-font; height: 100px; width: 300px; text-align: center; - font-size: 3.0em; - color: white; + font-size: 2.0em; + color: black; display: flex; justify-content: center; } -:local(.digit) { +:local(.char) { margin: 8px; } -:local(.digit-input) { +:local(.char-input) { + @extend %default-font; + font-weight: bold; outline-style: none; appearance:textfield; -moz-appearance:textfield; -webkit-appearance:textfield; background: transparent; - color: white; margin: 0; - font-size: 64pt; + font-size: 52pt; border: 0; width: 295px; letter-spacing: 0.08em; text-align: center; } -:local(.digit-input::placeholder) { +:local(.char-input::placeholder) { letter-spacing: 0; } +:local(.headset-icon) { + width: 64px; + height: 64px; + background-color: #333; + border-radius: 32px; + margin-bottom: 8px; + padding-right: 2px; + cursor: pointer; +} + +:local(.link-headset-footer-link) { + display: flex; + align-items: center; + + img { + width: 32px; + height: 32px; + border-radius: 32px; + margin-bottom: 8px; + margin-right: 16px; + padding-right: 2px; + cursor: pointer; + } +} diff --git a/src/assets/stylesheets/loader.scss b/src/assets/stylesheets/loader.scss index 74ccb011d068d9013c78f61817d4c4d1f24053ab..ff7e4098dca76956fe35013765d158930c3b6c5f 100644 --- a/src/assets/stylesheets/loader.scss +++ b/src/assets/stylesheets/loader.scss @@ -11,7 +11,7 @@ position: absolute; top: 0; left: 0; - background-color: black; + background-color: #d7e5ec; width: 100%; height: 100%; display: flex; @@ -21,8 +21,8 @@ } .loading-panel__logo { - width: 165px; - height: 33px; + width: 247px; + height: 57px; margin: 20px 0; } diff --git a/src/assets/stylesheets/profile.scss b/src/assets/stylesheets/profile.scss index e0434e022f35280e56848c6b12e609928567237a..4b01331d346967f8c3c5ca538f7fe4ab332ae205 100644 --- a/src/assets/stylesheets/profile.scss +++ b/src/assets/stylesheets/profile.scss @@ -1,4 +1,6 @@ -.profile-entry { +@import 'shared'; + +:local(.profile-entry) { position: absolute; top: 0; left: 0; @@ -8,20 +10,34 @@ align-items: center; display: flex; pointer-events: auto; + z-index: 10; + background-color: rgba(255, 255, 255, 0.95); + + :local(.logo) { + width: 150px; + position: absolute; + right: 32px; + bottom: 32px; + display: none; + + @media (min-width: 500px) { + display: block; + } + } - &__avatar-selector-container { + :local(.avatar-selector-container) { flex: 1; position: relative; - margin-bottom: 0.5em; width: 95%; text-align: center; + margin: 16px; .loading-panel { background: transparent; } } - &__avatar-selector { + :local(.avatar-selector) { border: none; width: 95%; height: 100%; @@ -29,18 +45,16 @@ position: relative; } - &__form { + :local(.form) { height: 100%; } - &__box { - border-radius: 18px; - box-shadow: 0px 0px 30px 1px #202020; + :local(.box) { display: flex; flex-direction: column; justify-content: space-between; align-items: center; - padding: 15px; + margin-top: 32px; flex: 1 1 100%; width: 60vw; min-width: 300px; @@ -49,7 +63,7 @@ max-height: 1000px; height: 90%; - &__links { + :local(.links) { display: flex; justify-content: center; width: 100%; @@ -57,102 +71,43 @@ a { margin: 0px 12px; - color: $light-text; + color: $dark-grey-text; } } - &--darkened { + :local(.darkened) { background-color: $darkest-transparent; } } - &__subtitle { + :local(.title) { + @extend %top-title; width: 100%; + color: black; text-align: center; - font-size: 1.2em; - color: $grey-text; } - &__display-name-label { + :local(.display-name) { font-size: 1.2em; margin-right: 0.5em; } - &__form-field-text { - @extend %rounded-border; + :local(.form-field-text) { + @extend %form-field-on-white; @extend %default-font; - color: $light-text; - font-size: 1.2em; - background-color: transparent; + font-size: 1.1em; + color: black; + outline: none; line-height: 2.0em; padding-left: 1.25em; padding-right: 1.25em; margin: 0.5em 0; } - &__form-submit { + :local(.form-submit) { @extend %bottom-action-button; margin: 0; min-height: max-content; + margin-top: 16px; } } - -.profile-info-header { - display: flex; - width: 100%; - justify-content: space-between; - min-height: 80px; - - &__menu-buttons { - display: flex; - margin: 0 0 0 20px; - - &__menu-button { - @extend %fa-icon-button; - margin-left: 16px; - - &__icon { - @extend %glass-icon; - @extend %fa-icon-big; - - background: transparent; - color: white; - } - } - } - - &__icon { - cursor: pointer; - width: 20px; - height: 20px; - margin: 15px; - } - - &__profile_display_name { - margin: 0 30px 0 0; - - @extend %glass-text; - - img { - @extend %glass-icon; - } - - margin-left: 16px; - cursor: pointer; - font-size: 1.2em; - line-height: 50px; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - display: flex; - align-items: center; - } - - &__app_name { - font-size: 1.8em; - padding-right: 18px; - line-height: 50px; - white-space: nowrap; - } -} - diff --git a/src/assets/stylesheets/scene-ui.scss b/src/assets/stylesheets/scene-ui.scss index 476f4e545012bb99411d6882950df6bcd3686217..2f48ccee5dae1e9abc7a63258d5955838f1c16c1 100644 --- a/src/assets/stylesheets/scene-ui.scss +++ b/src/assets/stylesheets/scene-ui.scss @@ -45,8 +45,7 @@ } :local(.logoTagline) { - color: black; - text-shadow: 0px 0px 10px #888; + @extend %background-agnostic; font-weight: bold; text-align: center; font-size: 1.2em; diff --git a/src/assets/stylesheets/shared.scss b/src/assets/stylesheets/shared.scss index 1a494fc65502ad7423f770204238e84c6a9fb8b8..8d93fe4a24e2c91e23ee200893383b99d8261941 100644 --- a/src/assets/stylesheets/shared.scss +++ b/src/assets/stylesheets/shared.scss @@ -1,13 +1,18 @@ $light-transparent: rgba(0, 0, 0, 0.2); +$white-transparent: rgba(255, 255, 255, 0.90); $dark-transparent: rgba(0, 0, 0, 0.4); $darker-transparent: rgba(0, 0, 0, 0.6); $darkest-transparent: rgba(0, 0, 0, 0.9); $grey-text: rgba(192, 192, 192, 1.0); +$dark-grey-text: rgba(64, 64, 64, 1.0); $light-text: rgba(240, 240, 240, 1.0); $light-grey: lightgrey; $dark-grey: rgba(128, 128, 128, 1.0); $darker-grey: rgba(64, 64, 64, 1.0); $darkest-grey: rgba(32, 32, 32, 1.0); +$action-color: #FF3464; +$action-color-light: #FF74A4; +$action-color-transparent: rgba(255, 52, 100, 0.9); %unselectable { -moz-user-select: none; @@ -17,13 +22,12 @@ $darkest-grey: rgba(32, 32, 32, 1.0); } %default-font { - font-family: 'Zilla Slab', sans-serif; + font-family: 'Open Sans', sans-serif; } %glass-text { - font-family: 'Zilla Slab', sans-serif; - text-shadow: 0px 0px 4px rgba(0, 0, 0, 1.0); - color: white; + font-family: 'Open Sans', sans-serif; + color: black; } %glass-icon { @@ -32,21 +36,35 @@ $darkest-grey: rgba(32, 32, 32, 1.0); } %rounded-border { - border: 3px solid white; + border: 1px solid #e2e2e2; box-sizing: border-box; - border-radius: 14px; + border-radius: 10px; +} + +%form-field-on-white { + border: 1px solid #e2e2e2; + background-color: white; + border-radius: 10px; } %big-action-button { @extend %default-font; outline-style: none; - font-size: 2em; + font-size: 18px; font-weight: bold; cursor: pointer; - border: 3px solid white; - border-radius: 26px; - padding: 12px 64px 12px 64px; - background: #2F80ED; + border-radius: 32px; + border: 0; + width: 350px; + padding: 18px; + text-decoration: none; + + @media (max-width: 768px) { + width: auto; + padding: 18px 48px; + } + + background: #FF3464; color: white; display: flex; align-items: center; @@ -62,33 +80,47 @@ $darkest-grey: rgba(32, 32, 32, 1.0); margin: 16px 0; } +%wide-button { + width: 350px; + + @media (max-width: 768px) { + width: 327px; + } +} + %action-button { @extend %default-font; + text-decoration: none; outline-style: none; font-weight: bold; cursor: pointer; - border: 3px solid white; - border-radius: 26px; - height: 64px; - padding: 12px; - background: #2F80ED; - font-size: 1.3em; + border: 0; + border-radius: 28px; + padding: 0px 18px; + + background: #FF3464; + font-size: 1em; color: white; display: flex; align-items: center; flex-direction: column; justify-content: center; min-width: 150px; + height: 48px; +} + +%action-button-selected { + background: white; + color: $action-color; } %bottom-action-button { @extend %bottom-button; - background: #2F80ED; - font-size: 1.3em; + background: #FF3464; } %top-title { - font-size: 1.3em; + font-size: 1.5em; font-weight: bold; } @@ -114,18 +146,23 @@ $darkest-grey: rgba(32, 32, 32, 1.0); -webkit-appearance: none; width: 2em; height: 2em; - border: 3px solid white; + border: 1px solid #e2e2e2; border-radius: 9px; vertical-align: sub; margin: 0 0.6em } %checkbox-checked { - border: 9px double white; - outline: 9px solid white; + border: 9px double #aaa; + outline: 9px solid #aaa; outline-offset: -18px; } +%background-agnostic { + color: black; + text-shadow: 0px 0px 10px #888; +} + %fa-icon-button { @extend %default-font; margin: 16px 0; @@ -133,7 +170,7 @@ $darkest-grey: rgba(32, 32, 32, 1.0); display: block; background: none; border: none; - color: white; + color: black; cursor: pointer; font-size: 0.8em; outline-style: none; @@ -162,7 +199,7 @@ $darkest-grey: rgba(32, 32, 32, 1.0); vertical-align: sub; line-height: 42px; svg { - margin-left: 0px; + margin-right: 2px; } } diff --git a/src/assets/stylesheets/ui-root.scss b/src/assets/stylesheets/ui-root.scss index 93771621fea6e39cdc19b15c559df4909698ced2..dd673065865abf8a4df55f07cb2d60c2211db259 100644 --- a/src/assets/stylesheets/ui-root.scss +++ b/src/assets/stylesheets/ui-root.scss @@ -1,6 +1,6 @@ @import 'shared'; -#ui-root .ui { +:local(.ui) { @extend %default-font; width: 100%; @@ -9,22 +9,21 @@ left: 0; position: absolute; pointer-events: none; - color: white; - &__help-icon { + :local(.help-icon) { @extend %fa-icon-button; pointer-events: auto; position: absolute; top: 0px; - left: 14px; + right: 14px; - &__icon { - background: rgba(33, 33, 33, 0.5); + i { + background: white; border-radius: 36px; display: inline-block; font-size: 22px; vertical-align: sub; - line-height: 38px; + line-height: 34px; border: 0; width: 36px; height: 36px; @@ -32,71 +31,71 @@ } } -.blurred { - filter: blur(5px) saturate(1.1) brightness(1.1); -} - -.ui-dialog { - display: grid; - grid-template-columns: 1fr 20px minmax(200px, 400px) 20px 1fr; - grid-template-rows: 1fr 20px minmax(200px, 600px) 20px 1fr; - width: 100%; +:local(.ui-dialog) { + position: absolute; + top: 0; + left: 0; height: 100%; + width: 100%; @extend %unselectable; - - &--darkened { - background-color: $dark-transparent; - } -} - -.ui-dialog-box { - grid-column: 3; - grid-row: 3; - position: relative; + flex-direction: column; + display: flex; + justify-content: flex-end; + align-items: center; } -.ui-dialog-box-contents { - background-color: $light-transparent; - border-radius: 18px; +:local(.ui-dialog-box-contents) { + background-color: $white-transparent; + border-radius: 18px 18px 0 0; width: 100%; - height: 100%; + max-width: 600px; + z-index: 2; - &--backgrounded { + :local(.backgrounded) { filter: blur(1px); opacity: 0.7; pointer-events: none; } } -.ui-interactive { +:local(.ui-interactive) { pointer-events: auto; @extend %unselectable; } -:local(.nag-button) { +:local(.invite-container) { @extend %unselectable; position: absolute; - top: 110px; + top: 0; left: 0; + margin-top: 16px; width: 100%; display: flex; + flex-direction: column; align-items: center; justify-content: center; - height: 80px; - pointer-events: none; + pointer-events: auto; button { @extend %action-button; pointer-events: auto; - padding: 16px 28px; - height: 58px; } - @media (max-height: 420px) { + @media (max-height: 320px) { display: none; } } +:local(.invite-container-inverted) { + button { + @extend %action-button-selected; + } +} + +:local(.invite-container-below-hud) { + margin-top: 100px; +} + :local(.nag-corner-button) { position: absolute; bottom: 42px; @@ -136,21 +135,24 @@ text-align: right; position: absolute; top: 0; - right: 16px; + left: 16px; margin: 16px 0; display: flex; align-items: center; justify-content: flex-end; font-size: 1.3em; - text-shadow: 0px 0px 4px rgba(0, 0, 0, 1.0); - -webkit-filter: drop-shadow( 0px 0px 3px #606060 ); - filter: drop-shadow( 0px 0px 3px #606060 ); + background-color: white; + border-radius: 24px; + font-weight: bold; + padding: 8px 18px; @media (min-width: 769px) and (min-height: 421px) { flex: 1; } @media (max-width: 768px) , (max-height: 420px) { - margin: 16px 8px; + margin: 16px 0; + margin-right: 0; + padding: 2px 8px; } :local(.occupant-count) { margin: 0 12px; diff --git a/src/assets/translations.data.json b/src/assets/translations.data.json index d29f96029b44573b4c01929289087abd1f92bcb7..6213bc174429e46a1ac7f770f64a56a907de3449 100644 --- a/src/assets/translations.data.json +++ b/src/assets/translations.data.json @@ -4,6 +4,8 @@ "auth.verified-title": "E-Mail Verified!", "auth.verified": "Your email has been verified!", "auth.spoke-verified": "You email has been verified! {br} You can now close this browser tab and return to Spoke.", + "entry.enter-room-title": "Lobby", + "entry.enter-room": "Enter Room", "entry.screen-prefix": "Enter on ", "entry.desktop-screen": "Screen", "entry.mobile-screen": "Phone", @@ -13,6 +15,7 @@ "entry.generic-subtitle-desktop": "Oculus or SteamVR", "entry.gearvr-prefix": "Enter on ", "entry.gearvr-medium": "Gear VR", + "entry.choose-device": "Choose Device", "entry.device-prefix-desktop": "Use a ", "entry.device-prefix-mobile": "Use a ", "entry.device-medium": "Mobile Headset", @@ -24,26 +27,26 @@ "entry.daydream-medium": "Daydream", "entry.daydream-via-chrome": "Using Google Chrome", "entry.invite-others": "invite others", - "entry.invite-others-nag": "invite others here", + "entry.invite-others-nag": "invite", "entry.invite-team-nag": "Invite a hubs team member", "entry.enable-screen-sharing": "Share my desktop", "entry.return-to-vr": "Enter in VR", - "profile.save": "save", + "profile.save": "Accept", "profile.display_name.validation_warning": "Alphanumerics and hyphens. At least 3 characters, no more than 32", - "profile.header": "Your display name:", + "profile.header": "Name & Avatar", "profile.avatar-selector.loading": "Loading Avatars...", "profile.terms_of_use": "Terms of Use", "profile.privacy_notice": "Privacy Notice", - "audio.title": "Test your audio", + "audio.title": "Audio Setup", "audio.subtitle-desktop": "Confirm HMD speaker output", "audio.subtitle-mobile": "Earphones are recommended", - "audio.enter-now": "enter now", + "audio.enter-now": "Enter Now", "audio.hmd-mic-warning": "Your HMD mic is not chosen", "audio.grant-title": "Grant mic permissions", "audio.grant-subtitle": "Mic access needed to be heard by others", "audio.granted-title": "Mic permissions granted", "audio.granted-subtitle": "You can still mute yourself in-game", - "audio.granted-next": "next", + "audio.granted-next": "Next", "exit.subtitle.exited": "Your session has ended. Refresh your browser to start a new one.", "exit.subtitle.closed": "This room is no longer available.", "exit.subtitle.full": "This room is full, please try again later.", @@ -54,32 +57,36 @@ "autoexit.subtitle": "You have started another session.", "autoexit.cancel": "CANCEL", "home.room_create_options": "options", - "home.room_create_button": "create a room", + "home.room_create_button": "Create Room", "home.create_name.validation_warning": "Invalid name, limited to 4 to 64 characters and limited symbols.", "home.join_us": "Join the Conversation", + "home.join_room": "Join Room", "home.report_issue": "Report Issues", - "home.source_link": "source", - "home.about_link": "about", + "home.source_link": "Source", + "home.about_link": "About", "home.privacy_notice": "Privacy Notice", "home.terms_of_use": "Terms of Use", "home.get_updates": "Get Updates", - "home.hero_title": "A new way to get together online.", - "home.hero_subtitle": "Laugh, play, get stuff done, or just hang out.", + "home.hero_title": "A new way to get together", "home.made_with_love": "made with 🦆 by ", "home.environment_author_by": " by ", "home.dialog.close": "CLOSE", - "home.have_entry_code": "have a link code?", "mailing_list.privacy_label": "I'm okay with Mozilla handling my info as explained in", "mailing_list.privacy_link": "this Privacy Notice", - "link.in_your_browser": "In your headset's browser, go to:", - "link.enter_code": "Then, enter this link code:", - "link.do_not_close": "Keep this dialog open to use this code.", - "link.link_page_header": "Enter your code:", - "link.dont_have_a_code": "Don't have a code?", - "link.create_a_room": "Create a Room", - "link.try_again": "We couldn't find that code. Please try again.", + "link.link_page_header_entry": "Enter your code:", + "link.link_page_header_headset": "Enter headset link code:", + "link.linking_a_headset": "Linking a Headset?", + "link.try_again": "We couldn't find that code.\nPlease try again.", "help.report_issue": "Report an Issue", "scene.logo_tagline": "A new way to get together", - "scene.create_button": "create a room with this scene" + "scene.create_button": "Create a room with this scene", + "link.in_your_browser": "In your headset's browser, go to:", + "link.enter_code": "Then, enter this one-time link code:", + "link.do_not_close": "Keep this open to use this code.", + "link.connect_headset": "Connect Mobile Headset", + "link.cancel": "cancel", + "invite.enter_via": "Enter via ", + "invite.and_enter_code": " with code:", + "invite.or_visit": "or visit" } } diff --git a/src/components/gltf-model-plus.js b/src/components/gltf-model-plus.js index e31e43dae45649514b8a888e5405909733cfc881..a753820a94b508de2d0ef3aff1a77d5178b7a054 100644 --- a/src/components/gltf-model-plus.js +++ b/src/components/gltf-model-plus.js @@ -1,3 +1,4 @@ +import nextTick from "../utils/next-tick"; import SketchfabZipWorker from "../workers/sketchfab-zip.worker.js"; import cubeMapPosX from "../assets/images/cubemap/posx.jpg"; import cubeMapNegX from "../assets/images/cubemap/negx.jpg"; @@ -190,12 +191,6 @@ function attachTemplate(root, name, templateRoot) { } } -function nextTick() { - return new Promise(resolve => { - setTimeout(resolve, 0); - }); -} - function getFilesFromSketchfabZip(src) { return new Promise((resolve, reject) => { const worker = new SketchfabZipWorker(); diff --git a/src/components/pitch-yaw-rotator.js b/src/components/pitch-yaw-rotator.js index 7af5799e72077977f81abd5ddff3a4686c46fe1f..bc1bd4f7876789f87eaa6d5e638d3efa3b3e0af4 100644 --- a/src/components/pitch-yaw-rotator.js +++ b/src/components/pitch-yaw-rotator.js @@ -1,4 +1,6 @@ const degToRad = THREE.Math.degToRad; +const radToDeg = THREE.Math.radToDeg; + AFRAME.registerComponent("pitch-yaw-rotator", { schema: { minPitch: { default: -50 }, @@ -17,6 +19,11 @@ AFRAME.registerComponent("pitch-yaw-rotator", { this.yaw += deltaYaw; }, + set(pitch, yaw) { + this.pitch = radToDeg(pitch); + this.yaw = radToDeg(yaw); + }, + tick() { this.el.object3D.rotation.set(degToRad(this.pitch), degToRad(this.yaw), 0); this.el.object3D.rotation.order = "YXZ"; diff --git a/src/components/scene-preview-camera.js b/src/components/scene-preview-camera.js index 0b7beaa29679518f63fceda44052f9efe37c2a1e..0f3b534f978a05023843c92fee41cdedb769406d 100644 --- a/src/components/scene-preview-camera.js +++ b/src/components/scene-preview-camera.js @@ -7,9 +7,12 @@ function lerp(start, end, t) { return (1 - t) * start + t * end; } -const DURATION = 90.0; - AFRAME.registerComponent("scene-preview-camera", { + schema: { + duration: { default: 90, type: "number" }, + positionOnly: { default: false, type: "boolean" } + }, + init: function() { this.startPoint = this.el.object3D.position.clone(); this.startRotation = new THREE.Quaternion(); @@ -26,10 +29,17 @@ AFRAME.registerComponent("scene-preview-camera", { this.startTime = new Date().getTime(); this.backwards = false; + this.ranOnePass = false; }, tick: function() { - const t = (new Date().getTime() - this.startTime) / (1000.0 * DURATION); + let t = (new Date().getTime() - this.startTime) / (1000.0 * this.data.duration); + + if (!this.ranOnePass) { + t = t * (2 - t); + } else { + t = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; + } const from = this.backwards ? this.targetPoint : this.startPoint; const to = this.backwards ? this.startPoint : this.targetPoint; @@ -40,9 +50,13 @@ AFRAME.registerComponent("scene-preview-camera", { THREE.Quaternion.slerp(fromRot, toRot, newRot, t); this.el.object3D.position.set(lerp(from.x, to.x, t), lerp(from.y, to.y, t), lerp(from.z, to.z, t)); - this.el.object3D.rotation.setFromQuaternion(newRot); - if (t >= 0.99) { + if (!this.data.positionOnly) { + this.el.object3D.rotation.setFromQuaternion(newRot); + } + + if (t >= 0.9999) { + this.ranOnePass = true; this.backwards = !this.backwards; this.startTime = new Date().getTime(); } diff --git a/src/components/stats-plus.css b/src/components/stats-plus.css index 3af930168e2721e3c3c27f901ffaa606892af6e8..f710791c800d43edb4f1ddfcf3962828ac017bb8 100644 --- a/src/components/stats-plus.css +++ b/src/components/stats-plus.css @@ -14,8 +14,8 @@ font-family: monospace; cursor: pointer; position: absolute; - top: 40px; - right: 16px; + top: 50px; + right: 6px; padding: 8px 12px; color: #aaa; font-size: 10px; diff --git a/src/hub.html b/src/hub.html index 4d297a996c477ad06f124674a1c3554959779b50..28b5c074dcc0900d5e38eab2e66178dee1a5f931 100644 --- a/src/hub.html +++ b/src/hub.html @@ -9,7 +9,7 @@ <link rel="shortcut icon" type="image/png" href="/favicon.ico"> <title>Get together | Hubs by Mozilla</title> - <link href="https://fonts.googleapis.com/css?family=Zilla+Slab:300,300i,400,400i,700" rel="stylesheet"> + <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,700" rel="stylesheet"> </head> <body> @@ -21,6 +21,7 @@ </audio> <a-scene + class="grab-cursor" renderer="antialias: true; gammaOutput: true; sortObjects: true; physicallyCorrectLights: true;" gamma-factor networked-scene="adapter: janus; audio: true; debug: true; connectOnLoad: false;" @@ -30,6 +31,7 @@ personal-space-bubble="debug: false;" vr-mode-ui="enabled: false" pinch-to-move + stats-plus="false" input-configurator=" gazeCursorRayObject: #player-camera; cursorController: #cursor-controller; @@ -165,7 +167,7 @@ <!-- HACK: rotation component above is required for its side effect of setting YXZ order --> <a-entity class="delete-button" visible-while-frozen> <a-entity mixin="rounded-text-button" remove-networked-object-button position="0 0 0"> </a-entity> - <a-entity text=" value:Delete; width:2.5; align:center;" text-raycast-hack position="0 0 0.01"></a-entity> + <a-entity text=" value:Remove; width:2.5; align:center;" text-raycast-hack position="0 0 0.01"></a-entity> </a-entity> </a-entity> </template> @@ -198,7 +200,7 @@ ></a-sphere> <a-entity class="delete-button" visible-while-frozen> <a-entity mixin="rounded-text-button" remove-networked-object-button position="0 0 0"> </a-entity> - <a-entity text=" value:Delete; width:2.5; align:center;" text-raycast-hack position="0 0 0.01"></a-entity> + <a-entity text=" value:Remove; width:2.5; align:center;" text-raycast-hack position="0 0 0.01"></a-entity> </a-entity> </a-entity> </template> @@ -220,7 +222,7 @@ > <a-entity class="delete-button" visible-while-frozen> <a-entity mixin="rounded-text-button" remove-networked-object-button position="0 0 0"> </a-entity> - <a-entity text=" value:Delete; width:2.5; align:center;" text-raycast-hack position="0 0 0.01"></a-entity> + <a-entity text=" value:Remove; width:2.5; align:center;" text-raycast-hack position="0 0 0.01"></a-entity> </a-entity> </a-entity> </template> @@ -324,13 +326,11 @@ <!-- Player Rig --> <a-entity id="player-rig" - networked="template: #remote-avatar-template; attachTemplateToLocal: false;" - spawn-controller="loadedEvent: bundleloaded; target: #environment-root" + spawn-controller="loadedEvent: entered; target: #player-rig" wasd-to-analog2d character-controller="pivot: #player-camera" ik-root player-info - networked-avatar cardboard-controls > <a-entity @@ -355,7 +355,6 @@ id="player-camera" class="camera" camera - position="0 1.6 0" personal-space-bubble="radius: 0.4;" pitch-yaw-rotator > @@ -459,7 +458,9 @@ id="environment-root" nav-mesh-helper static-body="shape: none;" - ></a-entity> + > + <a-entity id="environment-scene"/> + </a-entity> <a-entity super-spawner=" diff --git a/src/hub.js b/src/hub.js index ea29d071d41ef1cee9937331e192d7112dd4a75f..b694d396c36f4e8f202c90fbcbfa429c13c22d76 100644 --- a/src/hub.js +++ b/src/hub.js @@ -7,7 +7,6 @@ import "./utils/logging"; import { patchWebGLRenderingContext } from "./utils/webgl"; patchWebGLRenderingContext(); -import screenfull from "screenfull"; import "three/examples/js/loaders/GLTFLoader"; import "networked-aframe/src/index"; import "naf-janus-adapter"; @@ -21,13 +20,13 @@ import "aframe-motion-capture-components"; import "./utils/audio-context-fix"; import { getReticulumFetchUrl } from "./utils/phoenix-utils"; +import nextTick from "./utils/next-tick"; import trackpad_dpad4 from "./behaviours/trackpad-dpad4"; import trackpad_scrolling from "./behaviours/trackpad-scrolling"; import joystick_dpad4 from "./behaviours/joystick-dpad4"; import msft_mr_axis_with_deadzone from "./behaviours/msft-mr-axis-with-deadzone"; import { PressedMove } from "./activators/pressedmove"; import { ReverseY } from "./activators/reversey"; -import { ObjectContentOrigins } from "./object-types"; import "./activators/shortpress"; @@ -77,7 +76,8 @@ import HubChannel from "./utils/hub-channel"; import LinkChannel from "./utils/link-channel"; import { connectToReticulum } from "./utils/phoenix-utils"; import { disableiOSZoom } from "./utils/disable-ios-zoom"; -import { addMedia, resolveMedia } from "./utils/media-utils"; +import { resolveMedia } from "./utils/media-utils"; +import SceneEntryManager from "./scene-entry-manager"; import "./systems/nav"; import "./systems/personal-space-bubble"; @@ -122,10 +122,10 @@ import "./components/tools/networked-drawing"; import "./components/tools/drawing-manager"; import registerNetworkSchemas from "./network-schemas"; -import { inGameActions, config as inputConfig } from "./input-mappings"; +import { config as inputConfig } from "./input-mappings"; import registerTelemetry from "./telemetry"; -import { getAvailableVREntryTypes, VR_DEVICE_AVAILABILITY } from "./utils/vr-caps-detect.js"; +import { getAvailableVREntryTypes } from "./utils/vr-caps-detect.js"; import ConcurrentLoadDetector from "./utils/concurrent-load-detector.js"; import qsTruthy from "./utils/qs_truthy"; @@ -154,11 +154,50 @@ concurrentLoadDetector.start(); store.init(); -function mountUI(scene, props = {}) { +function getPlatformUnsupportedReason() { + if (typeof RTCDataChannelEvent === "undefined") return "no_data_channels"; + return null; +} + +function pollForSupportAvailability(callback) { + const availabilityUrl = getReticulumFetchUrl("/api/v1/support/availability"); + let isSupportAvailable = null; + + const updateIfChanged = () => + fetch(availabilityUrl).then(({ ok }) => { + if (isSupportAvailable === ok) return; + isSupportAvailable = ok; + callback(isSupportAvailable); + }); + + updateIfChanged(); + setInterval(updateIfChanged, 30000); +} + +function setupLobbyCamera() { + const camera = document.querySelector("#player-camera"); + const previewCamera = document.querySelector("#environment-scene").object3D.getObjectByName("scene-preview-camera"); + + if (previewCamera) { + camera.object3D.position.copy(previewCamera.position); + camera.object3D.rotation.copy(previewCamera.rotation); + camera.object3D.updateMatrix(); + } else { + const cameraPos = camera.object3D.position; + camera.object3D.position.set(cameraPos.x, 2.5, cameraPos.z); + } + + camera.setAttribute("scene-preview-camera", "positionOnly: true; duration: 60"); + camera.components["pitch-yaw-rotator"].set(camera.object3D.rotation.x / 2, camera.object3D.rotation.y); +} + +let uiProps = {}; + +function mountUI(props = {}) { + const scene = document.querySelector("a-scene"); const disableAutoExitOnConcurrentLoad = qsTruthy("allow_multi"); const forcedVREntryType = qs.get("vr_entry_type"); const enableScreenSharing = qsTruthy("enable_screen_sharing"); - const showProfileEntry = !store.state.activity.hasChangedName; ReactDOM.render( <UIRoot @@ -170,7 +209,6 @@ function mountUI(scene, props = {}) { forcedVREntryType, enableScreenSharing, store, - showProfileEntry, ...props }} />, @@ -178,350 +216,125 @@ function mountUI(scene, props = {}) { ); } -function requestFullscreen() { - if (screenfull.enabled && !screenfull.isFullscreen) screenfull.request(); +function remountUI(props) { + uiProps = { ...uiProps, ...props }; + mountUI(uiProps); } -const onReady = async () => { +async function handleHubChannelJoined(entryManager, hubChannel, data) { const scene = document.querySelector("a-scene"); - const hubChannel = new HubChannel(store); - const linkChannel = new LinkChannel(store); - - document.querySelector("canvas").classList.add("blurred"); - window.APP.scene = scene; - - registerNetworkSchemas(); - - let uiProps = { hubChannel, linkChannel }; - - mountUI(scene); - - const remountUI = props => { - uiProps = { ...uiProps, ...props }; - mountUI(scene, uiProps); - }; - - const applyProfileFromStore = playerRig => { - const displayName = store.state.profile.displayName; - playerRig.setAttribute("player-info", { - displayName, - avatarSrc: "#" + (store.state.profile.avatarId || "botdefault") - }); - const hudController = playerRig.querySelector("[hud-controller]"); - hudController.setAttribute("hud-controller", { showTip: !store.state.activity.hasFoundFreeze }); - document.querySelector("a-scene").emit("username-changed", { username: displayName }); - }; - - const pollForSupportAvailability = callback => { - let isSupportAvailable = null; - const availabilityUrl = getReticulumFetchUrl("/api/v1/support/availability"); - - const updateIfChanged = () => { - fetch(availabilityUrl).then(({ ok }) => { - if (isSupportAvailable !== ok) { - isSupportAvailable = ok; - callback(isSupportAvailable); - } - }); - }; - - updateIfChanged(); - setInterval(updateIfChanged, 30000); - }; - - const exitScene = () => { - if (NAF.connection.adapter && NAF.connection.adapter.localMediaStream) { - NAF.connection.adapter.localMediaStream.getTracks().forEach(t => t.stop()); - } - if (hubChannel) { - hubChannel.disconnect(); - } - const scene = document.querySelector("a-scene"); - if (scene) { - if (scene.renderer) { - scene.renderer.setAnimationLoop(null); // Stop animation loop, TODO A-Frame should do this - } - document.body.removeChild(scene); - } - document.body.removeEventListener("touchend", requestFullscreen); - }; - - const enterScene = async (mediaStream, enterInVR, hubId) => { - const scene = document.querySelector("a-scene"); - - // Get aframe inspector url using the webpack file-loader. - const aframeInspectorUrl = require("file-loader?name=assets/js/[name]-[hash].[ext]!aframe-inspector/dist/aframe-inspector.min.js"); - // Set the aframe-inspector url to our hosted copy. - scene.setAttribute("inspector", { url: aframeInspectorUrl }); - - if (!isBotMode) { - scene.classList.add("no-cursor"); - } - const playerRig = document.querySelector("#player-rig"); - document.querySelector("canvas").classList.remove("blurred"); - scene.render(); - - if (enterInVR) { - scene.enterVR(); - } else if (AFRAME.utils.device.isMobile()) { - document.body.addEventListener("touchend", requestFullscreen); - } - - AFRAME.registerInputActions(inGameActions, "default"); - scene.setAttribute("networked-scene", { - room: hubId, - serverURL: process.env.JANUS_SERVER - }); - - if (isDebug) { - scene.setAttribute("networked-scene", { debug: true }); - } - - scene.setAttribute("stats-plus", false); - - if (isMobile || qsTruthy("mobile")) { - playerRig.setAttribute("virtual-gamepad-controls", {}); - } + if (NAF.connection.isConnected()) { + // Send complete sync on phoenix re-join. + NAF.connection.entities.completeSync(null, true); + return; + } - const applyProfileOnPlayerRig = applyProfileFromStore.bind(null, playerRig); - applyProfileOnPlayerRig(); - store.addEventListener("statechanged", applyProfileOnPlayerRig); + const hub = data.hubs[0]; + const defaultSpaceTopic = hub.topics[0]; + const glbAsset = defaultSpaceTopic.assets.find(a => a.asset_type === "glb"); + const bundleAsset = defaultSpaceTopic.assets.find(a => a.asset_type === "gltf_bundle"); + const sceneUrl = (glbAsset || bundleAsset).src; + const hasExtension = /\.gltf/i.test(sceneUrl) || /\.glb/i.test(sceneUrl); + + console.log(`Scene URL: ${sceneUrl}`); + const environmentScene = document.querySelector("#environment-scene"); + + if (glbAsset || hasExtension) { + const resolved = await resolveMedia(sceneUrl, false, 0); + const gltfEl = document.createElement("a-entity"); + gltfEl.setAttribute("gltf-model-plus", { src: resolved.raw, useCache: false, inflate: true }); + gltfEl.addEventListener("model-loaded", () => environmentScene.emit("bundleloaded")); + environmentScene.appendChild(gltfEl); + } else { + // TODO kill bundles + environmentScene.setAttribute("gltf-bundle", `src: ${sceneUrl}`); + } - const avatarScale = parseInt(qs.get("avatar_scale"), 10); + remountUI({ hubId: hub.hub_id, hubName: hub.name, hubEntryCode: hub.entry_code }); - if (avatarScale) { - playerRig.setAttribute("scale", { x: avatarScale, y: avatarScale, z: avatarScale }); - } + scene.setAttribute("networked-scene", { + room: hub.hub_id, + serverURL: process.env.JANUS_SERVER, + debug: !!isDebug + }); - const videoTracks = mediaStream ? mediaStream.getVideoTracks() : []; - let sharingScreen = videoTracks.length > 0; + while (!scene.components["networked-scene"] || !scene.components["networked-scene"].data) await nextTick(); - const screenEntityId = `${NAF.clientId}-screen`; - let screenEntity = document.getElementById(screenEntityId); + scene.components["networked-scene"] + .connect() + .then(() => { + NAF.connection.adapter.reliableTransport = (clientId, dataType, data) => { + const payload = { dataType, data }; - scene.addEventListener("action_share_screen", () => { - sharingScreen = !sharingScreen; - if (sharingScreen) { - for (const track of videoTracks) { - mediaStream.addTrack(track); - } - } else { - for (const track of mediaStream.getVideoTracks()) { - mediaStream.removeTrack(track); + if (clientId) { + payload.clientId = clientId; } - } - NAF.connection.adapter.setLocalMediaStream(mediaStream); - screenEntity.setAttribute("visible", sharingScreen); - }); - - document.body.addEventListener("blocked", ev => { - NAF.connection.entities.removeEntitiesOfClient(ev.detail.clientId); - }); - - document.body.addEventListener("unblocked", ev => { - NAF.connection.entities.completeSync(ev.detail.clientId); - }); - - const offset = { x: 0, y: 0, z: -1.5 }; - const spawnMediaInfrontOfPlayer = (src, contentOrigin) => { - const { entity, orientation } = addMedia(src, "#interactable-media", contentOrigin, true); - orientation.then(or => { - entity.setAttribute("offset-relative-to", { - target: "#player-camera", - offset, - orientation: or - }); - }); - }; - - scene.addEventListener("add_media", e => { - const contentOrigin = e.detail instanceof File ? ObjectContentOrigins.FILE : ObjectContentOrigins.URL; - - spawnMediaInfrontOfPlayer(e.detail, contentOrigin); - }); - - scene.addEventListener("action_spawn_camera", () => { - const entity = document.createElement("a-entity"); - entity.setAttribute("networked", { template: "#interactable-camera" }); - entity.setAttribute("offset-relative-to", { - target: "#player-camera", - offset: { x: 0, y: 0, z: -1.5 } - }); - scene.appendChild(entity); - }); - - scene.addEventListener("object_spawned", e => { - if (hubChannel) { - hubChannel.sendObjectSpawnedEvent(e.detail.objectType); - } - }); - - document.addEventListener("paste", e => { - if (e.target.nodeName === "INPUT") return; + hubChannel.channel.push("naf", payload); + }; - const url = e.clipboardData.getData("text"); - const files = e.clipboardData.files && e.clipboardData.files; - if (url) { - spawnMediaInfrontOfPlayer(url, ObjectContentOrigins.URL); - } else { - for (const file of files) { - spawnMediaInfrontOfPlayer(file, ObjectContentOrigins.CLIPBOARD); - } + if (isBotMode) { + entryManager.enterSceneWhenLoaded(new MediaStream(), false); } - }); - - document.addEventListener("dragover", e => { - e.preventDefault(); - }); + }) + .catch(connectError => { + // hacky until we get return codes + const isFull = connectError.error && connectError.error.msg.match(/\bfull\b/i); + console.error(connectError); + remountUI({ roomUnavailableReason: isFull ? "full" : "connect_error" }); + entryManager.exitScene(); - document.addEventListener("drop", e => { - e.preventDefault(); - const url = e.dataTransfer.getData("url"); - const files = e.dataTransfer.files; - if (url) { - spawnMediaInfrontOfPlayer(url, ObjectContentOrigins.URL); - } else { - for (const file of files) { - spawnMediaInfrontOfPlayer(file, ObjectContentOrigins.FILE); - } - } + return; }); +} - if (!qsTruthy("offline")) { - document.body.addEventListener("connected", () => { - if (!isBotMode) { - hubChannel.sendEntryEvent().then(() => { - store.update({ activity: { lastEnteredAt: new Date().toISOString() } }); - }); - } - remountUI({ occupantCount: NAF.connection.adapter.publisher.initialOccupants.length + 1 }); - }); - - document.body.addEventListener("clientConnected", () => { - remountUI({ - occupantCount: Object.keys(NAF.connection.adapter.occupants).length + 1 - }); - }); - - document.body.addEventListener("clientDisconnected", () => { - remountUI({ - occupantCount: Object.keys(NAF.connection.adapter.occupants).length + 1 - }); - }); - - scene.components["networked-scene"].connect().catch(connectError => { - // hacky until we get return codes - const isFull = connectError.error && connectError.error.msg.match(/\bfull\b/i); - console.error(connectError); - remountUI({ roomUnavailableReason: isFull ? "full" : "connect_error" }); - exitScene(); - - return; - }); - - const sendHubDataMessage = function(clientId, dataType, data, reliable) { - const event = "naf"; - const payload = { dataType, data }; - - if (clientId != null) { - payload.clientId = clientId; - } - - if (reliable) { - hubChannel.channel.push(event, payload); - } else { - const topic = hubChannel.channel.topic; - const join_ref = hubChannel.channel.joinRef(); - hubChannel.channel.socket.push({ topic, event, payload, join_ref, ref: null }, false); - } - }; - - NAF.connection.adapter.reliableTransport = (clientId, dataType, data) => - sendHubDataMessage(clientId, dataType, data, true); - /*NAF.connection.adapter.unreliableTransport = (clientId, dataType, data) => - sendHubDataMessage(clientId, dataType, data, false);*/ +document.addEventListener("DOMContentLoaded", () => { + const scene = document.querySelector("a-scene"); + const hubChannel = new HubChannel(store); + const entryManager = new SceneEntryManager(hubChannel); + entryManager.init(); - if (isDebug) { - NAF.connection.adapter.session.options.verbose = true; - } + const linkChannel = new LinkChannel(store); - if (isBotMode) { - playerRig.setAttribute("avatar-replay", { - camera: "#player-camera", - leftController: "#player-left-controller", - rightController: "#player-right-controller" - }); - - const audioEl = document.createElement("audio"); - const audioInput = document.querySelector("#bot-audio-input"); - audioInput.onchange = () => { - audioEl.loop = true; - audioEl.muted = true; - audioEl.crossorigin = "anonymous"; - audioEl.src = URL.createObjectURL(audioInput.files[0]); - document.body.appendChild(audioEl); - }; - const dataInput = document.querySelector("#bot-data-input"); - dataInput.onchange = () => { - const url = URL.createObjectURL(dataInput.files[0]); - playerRig.setAttribute("avatar-replay", { recordingUrl: url }); - }; - await new Promise(resolve => audioEl.addEventListener("canplay", resolve)); - mediaStream.addTrack(audioEl.captureStream().getAudioTracks()[0]); - audioEl.play(); - } + window.APP.scene = scene; - if (mediaStream) { - NAF.connection.adapter.setLocalMediaStream(mediaStream); - - if (screenEntity) { - screenEntity.setAttribute("visible", sharingScreen); - } else if (sharingScreen) { - const sceneEl = document.querySelector("a-scene"); - screenEntity = document.createElement("a-entity"); - screenEntity.id = screenEntityId; - screenEntity.setAttribute("offset-relative-to", { - target: "#player-camera", - offset: "0 0 -2", - on: "action_share_screen" - }); - screenEntity.setAttribute("networked", { template: "#video-template" }); - sceneEl.appendChild(screenEntity); - } - } + registerNetworkSchemas(); + remountUI({ hubChannel, linkChannel, enterScene: entryManager.enterScene, exitScene: entryManager.exitScene }); - pollForSupportAvailability(isSupportAvailable => { - remountUI({ isSupportAvailable }); - }); - } - }; + pollForSupportAvailability(isSupportAvailable => remountUI({ isSupportAvailable })); - const getPlatformUnsupportedReason = () => { - if (typeof RTCDataChannelEvent === "undefined") { - return "no_data_channels"; - } + document.body.addEventListener("connected", () => + remountUI({ occupantCount: NAF.connection.adapter.publisher.initialOccupants.length + 1 }) + ); - return null; - }; + document.body.addEventListener("clientConnected", () => + remountUI({ + occupantCount: Object.keys(NAF.connection.adapter.occupants).length + 1 + }) + ); - remountUI({ enterScene, exitScene }); + document.body.addEventListener("clientDisconnected", () => + remountUI({ + occupantCount: Object.keys(NAF.connection.adapter.occupants).length + 1 + }) + ); const platformUnsupportedReason = getPlatformUnsupportedReason(); if (platformUnsupportedReason) { - remountUI({ platformUnsupportedReason: platformUnsupportedReason }); - exitScene(); + remountUI({ platformUnsupportedReason }); + entryManager.exitScene(); return; } if (qs.get("required_version") && process.env.BUILD_VERSION) { const buildNumber = process.env.BUILD_VERSION.split(" ", 1)[0]; // e.g. "123 (abcd5678)" + if (qs.get("required_version") !== buildNumber) { remountUI({ roomUnavailableReason: "version_mismatch" }); setTimeout(() => document.location.reload(), 5000); - exitScene(); + entryManager.exitScene(); return; } } @@ -529,39 +342,22 @@ const onReady = async () => { getAvailableVREntryTypes().then(availableVREntryTypes => { if (availableVREntryTypes.isInHMD) { remountUI({ availableVREntryTypes, forcedVREntryType: "vr" }); - } else if (availableVREntryTypes.gearvr === VR_DEVICE_AVAILABILITY.yes) { - remountUI({ availableVREntryTypes, forcedVREntryType: "gearvr" }); } else { remountUI({ availableVREntryTypes }); } }); - const environmentRoot = document.querySelector("#environment-root"); + document.querySelector("#environment-scene").addEventListener("bundleloaded", () => { + remountUI({ environmentSceneLoaded: true }); - const initialEnvironmentEl = document.createElement("a-entity"); - initialEnvironmentEl.addEventListener("bundleloaded", () => { - remountUI({ initialEnvironmentLoaded: true }); - // We never want to stop the render loop when were running in "bot" mode. - if (!isBotMode) { - // Stop rendering while the UI is up. We restart the render loop in enterScene. - // Wait a tick plus some margin so that the environments actually render. - setTimeout(() => scene.renderer.setAnimationLoop(null), 100); - } else { + setupLobbyCamera(); + + // Replace renderer with a noop renderer to reduce bot resource usage. + if (isBotMode) { const noop = () => {}; - // Replace renderer with a noop renderer to reduce bot resource usage. scene.renderer = { setAnimationLoop: noop, render: noop }; } }); - environmentRoot.appendChild(initialEnvironmentEl); - - const enterSceneWhenReady = hubId => { - const enterSceneImmediately = () => enterScene(new MediaStream(), false, hubId); - if (scene.hasLoaded) { - enterSceneImmediately(); - } else { - scene.addEventListener("loaded", enterSceneImmediately); - } - }; // Connect to reticulum over phoenix channels to get hub info. const hubId = qs.get("hub_id") || document.location.pathname.substring(1).split("/")[0]; @@ -569,49 +365,16 @@ const onReady = async () => { const socket = connectToReticulum(isDebug); const channel = socket.channel(`hub:${hubId}`, {}); - let loadedSceneUrl = null; - - // This routine needs to handle re-joins. - const handleJoinedHubChannel = async data => { - const hub = data.hubs[0]; - const defaultSpaceTopic = hub.topics[0]; - const glbAsset = defaultSpaceTopic.assets.find(a => a.asset_type === "glb"); - const bundleAsset = defaultSpaceTopic.assets.find(a => a.asset_type === "gltf_bundle"); - const sceneUrl = (glbAsset || bundleAsset).src; - const hasExtension = /\.gltf/i.test(sceneUrl) || /\.glb/i.test(sceneUrl); - - console.log(`Scene URL: ${sceneUrl}`); - - if (glbAsset || hasExtension) { - if (loadedSceneUrl !== sceneUrl) { - const resolved = await resolveMedia(sceneUrl, false, 0); - const gltfEl = document.createElement("a-entity"); - gltfEl.setAttribute("gltf-model-plus", { src: resolved.raw, useCache: false, inflate: true }); - gltfEl.addEventListener("model-loaded", () => initialEnvironmentEl.emit("bundleloaded")); - initialEnvironmentEl.appendChild(gltfEl); - loadedSceneUrl = sceneUrl; - } - } else { - // TODO kill bundles - initialEnvironmentEl.setAttribute("gltf-bundle", `src: ${sceneUrl}`); - } - - remountUI({ hubId: hub.hub_id, hubName: hub.name }); - hubChannel.setPhoenixChannel(channel); - if (isBotMode) enterSceneWhenReady(hub.hub_id); - - if (NAF.connection.adapter) { - // Send complete sync on phoenix re-join. - NAF.connection.entities.completeSync(null, true); - } - }; channel .join() - .receive("ok", handleJoinedHubChannel) + .receive("ok", async data => { + hubChannel.setPhoenixChannel(channel); + await handleHubChannelJoined(entryManager, hubChannel, data); + }) .receive("error", res => { if (res.reason === "closed") { - exitScene(); + entryManager.exitScene(); remountUI({ roomUnavailableReason: "closed" }); } @@ -619,12 +382,9 @@ const onReady = async () => { }); channel.on("naf", data => { - if (NAF.connection.adapter) { - NAF.connection.adapter.onData(data); - } + if (!NAF.connection.adapter) return; + NAF.connection.adapter.onData(data); }); linkChannel.setSocket(socket); -}; - -document.addEventListener("DOMContentLoaded", onReady); +}); diff --git a/src/index.html b/src/index.html index f7807252dc6d6756e8d95102347eeeb84e450bce..29c074aab067736ba69f5f80191c9d78643ebd25 100644 --- a/src/index.html +++ b/src/index.html @@ -6,7 +6,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="shortcut icon" type="image/png" href="/favicon.ico"/> <title>Get together | Hubs by Mozilla</title> - <link href="https://fonts.googleapis.com/css?family=Zilla+Slab:300,300i,400,400i,700" rel="stylesheet"> + <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,700" rel="stylesheet"> </head> <body> diff --git a/src/index.js b/src/index.js index fa64e8923edfb8f103f8ac0d98342e759ad59304..5c9f008ccd2e110ba4753133005ff42c4e8884c7 100644 --- a/src/index.js +++ b/src/index.js @@ -13,7 +13,7 @@ const sceneId = qs.get("scene_id") || (pathname.startsWith("/scenes/") && pathna const root = ( <HomeRoot initialEnvironment={qs.get("initial_environment")} - sceneId={sceneId} + sceneId={sceneId || ""} authVerify={qs.has("auth_topic")} authTopic={qs.get("auth_topic")} authToken={qs.get("auth_token")} diff --git a/src/link.html b/src/link.html index 954061491faa3b257adedf87df06a26cfc58f79b..f2d46396072f3e580a5543261b534d3b382aa48b 100644 --- a/src/link.html +++ b/src/link.html @@ -6,7 +6,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="shortcut icon" type="image/png" href="/favicon.ico"/> <title>Enter Code | Hubs by Mozilla</title> - <link href="https://fonts.googleapis.com/css?family=Zilla+Slab:300,300i,400,400i,700" rel="stylesheet"> + <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,700" rel="stylesheet"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> </head> diff --git a/src/react-components/2d-hud.js b/src/react-components/2d-hud.js index 6c31677f58655bec2bb4ef364bd83a078f9e3fc2..74577ff1ec0291d8bd14665fe0a7054c8a0f70e3 100644 --- a/src/react-components/2d-hud.js +++ b/src/react-components/2d-hud.js @@ -3,10 +3,11 @@ import PropTypes from "prop-types"; import cx from "classnames"; import styles from "../assets/stylesheets/2d-hud.scss"; +import uiStyles from "../assets/stylesheets/ui-root.scss"; const TopHUD = ({ muted, frozen, onToggleMute, onToggleFreeze, onSpawnPen, onSpawnCamera }) => ( <div className={cx(styles.container, styles.top, styles.unselectable)}> - <div className={cx("ui-interactive", styles.panel, styles.left)}> + <div className={cx(uiStyles.uiInteractive, styles.panel, styles.left)}> <div className={cx(styles.iconButton, styles.mute, { [styles.active]: muted })} title={muted ? "Unmute Mic" : "Mute Mic"} @@ -14,11 +15,13 @@ const TopHUD = ({ muted, frozen, onToggleMute, onToggleFreeze, onSpawnPen, onSpa /> </div> <div - className={cx("ui-interactive", styles.iconButton, styles.large, styles.freeze, { [styles.active]: frozen })} + className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.freeze, { + [styles.active]: frozen + })} title={frozen ? "Resume" : "Pause"} onClick={onToggleFreeze} /> - <div className={cx("ui-interactive", styles.panel, styles.right)}> + <div className={cx(uiStyles.uiInteractive, styles.panel, styles.right)}> <div className={cx(styles.iconButton, styles.spawn_pen)} title={"Drawing Pen"} onClick={onSpawnPen} /> <div className={cx(styles.iconButton, styles.spawn_camera)} title={"Camera"} onClick={onSpawnCamera} /> </div> @@ -37,7 +40,7 @@ TopHUD.propTypes = { const BottomHUD = ({ onCreateObject, showPhotoPicker, onMediaPicked }) => ( <div className={cx(styles.container, styles.column, styles.bottom, styles.unselectable)}> {showPhotoPicker ? ( - <div className={cx("ui-interactive", styles.panel, styles.up)}> + <div className={cx(uiStyles.uiInteractive, styles.panel, styles.up)}> <input id="media-picker-input" className={cx(styles.hide)} @@ -59,7 +62,7 @@ const BottomHUD = ({ onCreateObject, showPhotoPicker, onMediaPicked }) => ( )} <div> <div - className={cx("ui-interactive", styles.iconButton, styles.large, styles.createObject)} + className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.createObject)} title={"Create Object"} onClick={onCreateObject} /> diff --git a/src/react-components/avatar-selector.js b/src/react-components/avatar-selector.js index 81b30d6057b436a296b9856f742b1bb907514495..c08a292e9c332f320fb8c3bd2445e5a7c336dac3 100644 --- a/src/react-components/avatar-selector.js +++ b/src/react-components/avatar-selector.js @@ -5,9 +5,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faAngleLeft } from "@fortawesome/free-solid-svg-icons/faAngleLeft"; import { faAngleRight } from "@fortawesome/free-solid-svg-icons/faAngleRight"; -// TODO: we should make a bundle for avatar picker with it's own geometry, for now just use the indoor part of the meting room -const meetingSpace = "https://asset-bundles-prod.reticulum.io/rooms/meetingroom/MeetingSpace1_mesh-d48250ebc6.gltf"; - class AvatarSelector extends Component { static propTypes = { avatars: PropTypes.array, @@ -159,10 +156,7 @@ class AvatarSelector extends Component { return ( <div className="avatar-selector"> <a-scene vr-mode-ui="enabled: false" ref={sce => (this.scene = sce)}> - <a-assets> - {avatarAssets} - <a-asset-item id="meeting-space1-mesh" response-type="arraybuffer" src={meetingSpace} /> - </a-assets> + <a-assets>{avatarAssets}</a-assets> <a-entity rotation={`0 ${initialRotation} 0`}> <a-animation diff --git a/src/react-components/create-object-dialog.js b/src/react-components/create-object-dialog.js index 2636230326838dc4bcabbb73e8d38a8674b2f4da..5ccfe2ce692a234fff6e76d8657516c1ac1d8816 100644 --- a/src/react-components/create-object-dialog.js +++ b/src/react-components/create-object-dialog.js @@ -126,7 +126,7 @@ export default class CreateObjectDialog extends Component { </div> <div className={styles.buttons}> <button className={styles.actionButton}> - <span>create</span> + <span>Create</span> </button> </div> {this.state.attributionImage ? ( diff --git a/src/react-components/help-dialog.js b/src/react-components/help-dialog.js index dc0ba425a3f4e9cd12db05c503280fc1c19ce3d7..7e1fb4ba46801f921159a853acc41b5737a64331 100644 --- a/src/react-components/help-dialog.js +++ b/src/react-components/help-dialog.js @@ -12,23 +12,14 @@ export default class HelpDialog extends Component { Use your controller's action button to teleport from place to place. If it has a trigger, use it to pick up objects. </p> - <p style={{ textAlign: "center" }}> - In VR, <b>look up</b> to find your menu: - <img - className="info-dialog__help__hud" - src="../assets/images/help-hud.png" - srcSet="../assets/images/help-hud@2x.png 2x" - /> - </p> <p> - The <b>Mic Toggle</b> mutes your mic. + In VR, <b>look up</b> to find your menu. </p> <p> - The <b>Pause/Resume Toggle</b> pauses all other avatars. You can then block them from having further - interactions with you. + The <b>Mic Toggle</b> mutes your mic. </p> <p> - The <b>Bubble Toggle</b> hides avatars that enter your personal space. + The <b>Pause/Resume Toggle</b> pauses all other avatars and lets you block others or remove objects. </p> <p className="dialog__box__contents__links"> <a target="_blank" rel="noopener noreferrer" href="https://github.com/mozilla/hubs/blob/master/TERMS.md"> diff --git a/src/react-components/home-root.js b/src/react-components/home-root.js index 266adef27d395461ceced2a525361f38c076ae94..f4bb05be8bc7f96a39f34b4801d33c710a30f588 100644 --- a/src/react-components/home-root.js +++ b/src/react-components/home-root.js @@ -6,6 +6,8 @@ import en from "react-intl/locale-data/en"; import { lang, messages } from "../utils/i18n"; import homeVideoWebM from "../assets/video/home.webm"; import homeVideoMp4 from "../assets/video/home.mp4"; +import hubLogo from "../assets/images/hub-preview-light-no-shadow.png"; +import mozLogo from "../assets/images/moz-logo-black.png"; import classNames from "classnames"; import { ENVIRONMENT_URLS } from "../assets/environments/environments"; import { connectToReticulum } from "../utils/phoenix-utils"; @@ -182,19 +184,17 @@ class HomeRoot extends Component { <div className={styles.home}> <div className={mainContentClassNames}> <div className={styles.headerContent}> - <div className={styles.titleAndNav} onClick={() => (document.location = "/")}> - <div className={styles.hubs}>hubs</div> - <div className={styles.preview}>preview</div> + <div className={styles.titleAndNav}> <div className={styles.links}> - <a href="https://github.com/mozilla/hubs" rel="noreferrer noopener"> - <FormattedMessage id="home.source_link" /> - </a> <a href="https://blog.mozvr.com/introducing-hubs-a-new-way-to-get-together-online/" rel="noreferrer noopener" > <FormattedMessage id="home.about_link" /> </a> + <a href="https://github.com/mozilla/hubs" rel="noreferrer noopener"> + <FormattedMessage id="home.source_link" /> + </a> </div> </div> <div className={styles.ident} /> @@ -211,20 +211,21 @@ class HomeRoot extends Component { </a> </div> <div className={styles.container}> + <img className={styles.logo} src={hubLogo} /> <div className={styles.title}> <FormattedMessage id="home.hero_title" /> </div> - <div className={styles.subtitle}> - <FormattedMessage id="home.hero_subtitle" /> - </div> </div> <div className={styles.create}> - {this.state.environments.length > 0 && ( - <HubCreatePanel - initialEnvironment={this.props.initialEnvironment} - environments={this.state.environments} - /> - )} + <HubCreatePanel + initialEnvironment={this.props.initialEnvironment} + environments={this.state.environments} + /> + </div> + <div className={styles.joinButton}> + <a href="/link"> + <FormattedMessage id="home.join_room" /> + </a> </div> </div> <div className={styles.footerContent}> @@ -270,12 +271,8 @@ class HomeRoot extends Component { > <FormattedMessage id="home.privacy_notice" /> </a> - </div> - <div className={styles.bottom}> - <div> - <FormattedMessage id="home.made_with_love" /> - <span style={{ fontWeight: "bold", color: "white" }}>Mozilla</span> - </div> + + <img className={styles.mozLogo} src={mozLogo} /> </div> </div> </div> diff --git a/src/react-components/hub-create-panel.js b/src/react-components/hub-create-panel.js index 0492946479fd0302d94ebfb9e20900633b000efc..40550c9a025acfeed3c29f1d42c8e37cbfef1bf2 100644 --- a/src/react-components/hub-create-panel.js +++ b/src/react-components/hub-create-panel.js @@ -162,7 +162,7 @@ class HubCreatePanel extends Component { if (!this.state.ready) return null; if (this.props.environments.length == 0) { - return <div />; + return <div className={styles.placeholder} />; } const environment = this.props.environments[this.state.environmentIndex]; @@ -236,13 +236,6 @@ class HubCreatePanel extends Component { <FormattedMessage id="home.room_create_button" /> </button> </div> - <div className={styles.linkCode}> - <div> - <a className={styles.link} href="/link" rel="nofollow"> - <FormattedMessage id="home.have_entry_code" /> - </a> - </div> - </div> </div> </div> </form> diff --git a/src/react-components/invite-dialog.js b/src/react-components/invite-dialog.js index e01fc1282cf835e8e5a65363b3dfc6ee0f4e891f..f275dbd4246006e91fd5b6cdfb27a85999936cc4 100644 --- a/src/react-components/invite-dialog.js +++ b/src/react-components/invite-dialog.js @@ -1,56 +1,73 @@ import React, { Component } from "react"; +import PropTypes from "prop-types"; import copy from "copy-to-clipboard"; -import DialogContainer from "./dialog-container.js"; +import { FormattedMessage } from "react-intl"; + +import styles from "../assets/stylesheets/invite-dialog.scss"; + +function pad(num, size) { + let s = `${num}`; + while (s.length < size) s = `0${s}`; + return s; +} export default class InviteDialog extends Component { - state = { - copyLinkButtonText: "copy" + static propTypes = { + entryCode: PropTypes.number, + allowShare: PropTypes.bool, + onClose: PropTypes.func }; - constructor(props) { - super(props); - const loc = document.location; - this.shareLink = `${loc.protocol}//${loc.host}${loc.pathname}`; - } - - copyLinkClicked = link => { - copy(link); - this.setState({ copyLinkButtonText: "copied!" }); + state = { + linkButtonText: navigator.share && this.props.allowShare ? "share" : "copy" }; - shareLinkClicked = () => { - navigator.share({ - title: document.title, - url: this.shareLink - }); + linkClicked = link => { + if (navigator.share && this.props.allowShare) { + navigator.share({ title: document.title, url: link }); + this.props.onClose(); + } else { + copy(link); + this.setState({ linkButtonText: "copied!" }); + } }; render() { + const { entryCode } = this.props; + + const entryCodeString = pad(entryCode, 6); + const shareLink = `hub.link/${entryCodeString}`; + return ( - <DialogContainer title="Invite Others" {...this.props}> + <div className={styles.dialog}> + <div className={styles.attachPoint} /> + <div className={styles.close} onClick={() => this.props.onClose()}> + <span>×</span> + </div> <div> - <div>Just share the link and they'll join you:</div> - <div className="invite-form"> - <input - type="text" - readOnly - onFocus={e => e.target.select()} - value={this.shareLink} - className="invite-form__link_field" - /> - <div className="invite-form__buttons"> - {navigator.share && ( - <button className="invite-form__action-button" onClick={this.shareLinkClicked}> - <span>share</span> - </button> - )} - <button className="invite-form__action-button" onClick={this.copyLinkClicked.bind(this, this.shareLink)}> - <span>{this.state.copyLinkButtonText}</span> - </button> + <FormattedMessage id="invite.enter_via" /> + <a href="https://hub.link" target="_blank" className={styles.hubLinkLink} rel="noopener noreferrer"> + hub.link + </a> + <FormattedMessage id="invite.and_enter_code" /> + </div> + <div className={styles.code}> + {entryCodeString.split("").map((d, i) => ( + <div className={styles.digit} key={`link_code_${i}`}> + {d} </div> - </div> + ))} + </div> + <div> + <FormattedMessage id="invite.or_visit" /> + </div> + <div className={styles.domain}> + <input type="text" readOnly onFocus={e => e.target.select()} value={shareLink} /> </div> - </DialogContainer> + <button className={styles.linkButton} onClick={this.linkClicked.bind(this, "https://" + shareLink)}> + <span>{this.state.linkButtonText}</span> + </button> + </div> ); } } diff --git a/src/react-components/link-dialog.js b/src/react-components/link-dialog.js index 456090494e4cc9e00799d697530adbe460ae4bd2..d3f45051d33736e65ecde5805eee3bb465959e07 100644 --- a/src/react-components/link-dialog.js +++ b/src/react-components/link-dialog.js @@ -2,57 +2,70 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import classNames from "classnames"; import { FormattedMessage } from "react-intl"; -import DialogContainer from "./dialog-container.js"; +import LinkDialogHeader from "../assets/images/link_dialog_header.svg"; import styles from "../assets/stylesheets/link-dialog.scss"; export default class LinkDialog extends Component { static propTypes = { - linkCode: PropTypes.string + linkCode: PropTypes.string, + onClose: PropTypes.func }; render() { - const { linkCode, ...other } = this.props; - if (!linkCode) { - return ( - <DialogContainer title="Open on Headset" {...other}> - <div> - <div className={classNames("loading-panel", styles.codeLoadingPanel)}> - <div className="loader-wrap"> - <div className="loader"> - <div className="loader-center" /> - </div> - </div> - </div> - </div> - </DialogContainer> - ); - } + const { linkCode } = this.props; return ( - <DialogContainer title="Open on Headset" {...other}> + <div className={styles.dialog}> + <div className={styles.close} onClick={() => this.props.onClose()}> + <span>×</span> + </div> <div> - <div> - <FormattedMessage id="link.in_your_browser" /> - </div> - <a href="https://hub.link" className={styles.domain} target="_blank" rel="noopener noreferrer"> - hub.link - </a> - <div> - <FormattedMessage id="link.enter_code" /> - </div> - <div className={styles.code}> - {linkCode.split("").map((d, i) => ( - <span className={styles.digit} key={`link_code_${i}`}> - {d} - </span> - ))} - </div> - <div className={styles.keepOpen}> - <FormattedMessage id="link.do_not_close" /> - </div> + {!linkCode && ( + <div> + <div className={classNames("loading-panel", styles.codeLoadingPanel)}> + <div className="loader-wrap"> + <div className="loader"> + <div className="loader-center" /> + </div> + </div> + </div> + </div> + )} + {linkCode && ( + <div className={styles.contents}> + <img className={styles.imageHeader} src={LinkDialogHeader} /> + <div className={styles.header}> + <FormattedMessage id="link.connect_headset" /> + </div> + <div> + <FormattedMessage id="link.in_your_browser" /> + </div> + <a href="https://hub.link" className={styles.domain} target="_blank" rel="noopener noreferrer"> + hub.link + </a> + <div> + <FormattedMessage id="link.enter_code" /> + </div> + {linkCode && ( + <div className={styles.code}> + {linkCode.split("").map((d, i) => ( + <span className={styles.digit} key={`link_code_${i}`}> + {d} + </span> + ))} + </div> + )} + <div className={styles.keepOpen}> + <FormattedMessage id="link.do_not_close" /> + </div> + <button className={styles.closeButton} onClick={() => this.props.onClose()}> + <FormattedMessage id="link.cancel" /> + </button> + </div> + )} </div> - </DialogContainer> + </div> ); } } diff --git a/src/react-components/link-root.js b/src/react-components/link-root.js index 84b7101637357253da095a0c69bd0f20382318e4..bd9f5bcdf3e95e42fea54e7ece99b0d31ade0ac3 100644 --- a/src/react-components/link-root.js +++ b/src/react-components/link-root.js @@ -7,11 +7,14 @@ import { lang, messages } from "../utils/i18n"; import classNames from "classnames"; import styles from "../assets/stylesheets/link.scss"; import { disableiOSZoom } from "../utils/disable-ios-zoom"; +import HeadsetIcon from "../assets/images/generic_vr_entry.svg"; -const MAX_DIGITS = 4; +const MAX_DIGITS = 6; +const MAX_LETTERS = 4; addLocaleData([...en]); disableiOSZoom(); +const hasTouchEvents = "ontouchstart" in document.documentElement; class LinkRoot extends Component { static propTypes = { @@ -21,7 +24,8 @@ class LinkRoot extends Component { }; state = { - enteredDigits: "", + entered: "", + isAlphaMode: false, failedAtLeastOnce: false }; @@ -35,34 +39,47 @@ class LinkRoot extends Component { handleKeyDown = e => { // Number keys 0-9 - if (e.keyCode < 48 || e.keyCode > 57) { + if ((e.keyCode < 48 || e.keyCode > 57) && !this.state.isAlphaMode) { + return; + } + + // Alpha keys A-I + if ((e.keyCode < 65 || e.keyCode > 73) && this.state.isAlphaMode) { return; } e.preventDefault(); e.stopPropagation(); - this.addDigit(e.keyCode - 48); + if (this.state.isAlphaMode) { + this.addToEntry("IHGFEDCBA"[73 - e.keyCode]); + } else { + this.addToEntry(e.keyCode - 48); + } + }; + + maxAllowedChars = () => { + return this.state.isAlphaMode ? MAX_LETTERS : MAX_DIGITS; }; - addDigit = digit => { - if (this.state.enteredDigits.length >= MAX_DIGITS) return; - const newDigits = `${this.state.enteredDigits}${digit}`; + addToEntry = ch => { + if (this.state.entered.length >= this.maxAllowedChars()) return; + const newChars = `${this.state.entered}${ch}`; - if (newDigits.length === MAX_DIGITS) { - this.attemptLink(newDigits); + if (newChars.length === this.maxAllowedChars()) { + this.attemptLookup(newChars); } - this.setState({ enteredDigits: newDigits }); + this.setState({ entered: newChars }); }; - removeDigit = () => { - const enteredDigits = this.state.enteredDigits; - if (enteredDigits.length === 0) return; - this.setState({ enteredDigits: enteredDigits.substring(0, enteredDigits.length - 1) }); + removeChar = () => { + const entered = this.state.entered; + if (entered.length === 0) return; + this.setState({ entered: entered.substring(0, entered.length - 1) }); }; - attemptLink = code => { + attemptLink = async code => { this.props.linkChannel .attemptLink(code) .then(response => { @@ -80,7 +97,7 @@ class LinkRoot extends Component { } }) .catch(e => { - this.setState({ failedAtLeastOnce: true, enteredDigits: "" }); + this.setState({ failedAtLeastOnce: true, entered: "" }); if (!(e instanceof Error && (e.message === "in_use" || e.message === "failed"))) { throw e; @@ -88,6 +105,31 @@ class LinkRoot extends Component { }); }; + attemptEntry = async code => { + const url = "https://hub.link/" + code; + const res = await fetch(url); + + if (res.status >= 400) { + this.setState({ failedAtLeastOnce: true, entered: "" }); + } else { + document.location = url; + } + }; + + attemptLookup = async code => { + if (this.state.isAlphaMode) { + // Headset link code + this.attemptLink(code); + } else { + // Room entry code + this.attemptEntry(code); + } + }; + + toggleMode = () => { + this.setState({ isAlphaMode: !this.state.isAlphaMode, entered: "", failedAtLeastOnce: false }); + }; + render() { // Note we use type "tel" for the input due to https://bugzilla.mozilla.org/show_bug.cgi?id=1005603 @@ -95,7 +137,10 @@ class LinkRoot extends Component { <IntlProvider locale={lang} messages={messages}> <div className={styles.link}> <div className={styles.linkContents}> - {this.state.enteredDigits.length === MAX_DIGITS && ( + <div className={styles.logo}> + <img src="../assets/images/hub-preview-light-no-shadow.png" /> + </div> + {this.state.entered.length === this.maxAllowedChars() && ( <div className={classNames("loading-panel", styles.codeLoadingPanel)}> <div className="loader-wrap"> <div className="loader"> @@ -107,72 +152,108 @@ class LinkRoot extends Component { <div className={styles.enteredContents}> <div className={styles.header}> - <FormattedMessage id={this.state.failedAtLeastOnce ? "link.try_again" : "link.link_page_header"} /> + <FormattedMessage + id={ + this.state.failedAtLeastOnce + ? "link.try_again" + : "link.link_page_header_" + (!this.state.isAlphaMode ? "entry" : "headset") + } + /> </div> - <div className={styles.enteredDigits}> + <div className={styles.entered}> <input - className={styles.digitInput} - type="tel" - pattern="[0-9]*" - value={this.state.enteredDigits} + className={styles.charInput} + type={this.state.isAlphaMode ? "text" : "tel"} + pattern="[0-9A-I]*" + value={this.state.entered} onChange={ev => { - this.setState({ enteredDigits: ev.target.value }); + if (!this.state.isAlphaMode && ev.target.value.match(/[a-z]/i)) { + this.setState({ isAlphaMode: true }); + } + + this.setState({ entered: ev.target.value.toUpperCase() }); }} - placeholder="- - - -" + placeholder={this.state.isAlphaMode ? "- - - -" : "- - - - - -"} /> </div> <div className={styles.enteredFooter}> - <span> - <FormattedMessage id="link.dont_have_a_code" /> - </span>{" "} - <span> - <a href="/"> - <FormattedMessage id="link.create_a_room" /> - </a> - </span> - <img className={styles.entryFooterImage} src="../assets/images/logo.svg" /> + {!this.state.isAlphaMode && ( + <img onClick={() => this.toggleMode()} src={HeadsetIcon} className={styles.headsetIcon} /> + )} + {!this.state.isAlphaMode && ( + <span> + <a href="#" onClick={() => this.toggleMode()}> + <FormattedMessage id="link.linking_a_headset" /> + </a> + </span> + )} </div> </div> <div className={styles.keypad}> - {[1, 2, 3, 4, 5, 6, 7, 8, 9].map((d, i) => ( + {(this.state.isAlphaMode + ? ["A", "B", "C", "D", "E", "F", "G", "H", "I"] + : [1, 2, 3, 4, 5, 6, 7, 8, 9] + ).map((d, i) => ( <button - disabled={this.state.enteredDigits.length === MAX_DIGITS} - key={`digit_${i}`} + disabled={this.state.entered.length === this.maxAllowedChars()} + key={`char_${i}`} className={styles.keypadButton} - onClick={() => this.addDigit(d)} + onClick={() => { + if (!hasTouchEvents) this.addToEntry(d); + }} + onTouchStart={() => this.addToEntry(d)} > {d} </button> ))} <button - disabled={this.state.enteredDigits.length === MAX_DIGITS} - className={classNames(styles.keypadButton, styles.keypadZeroButton)} - onClick={() => this.addDigit(0)} + className={classNames(styles.keypadButton, styles.keypadToggleMode)} + onTouchStart={() => this.toggleMode()} + onClick={() => { + if (!hasTouchEvents) this.toggleMode(); + }} > - 0 + {this.state.isAlphaMode ? "123" : "ABC"} </button> + {!this.state.isAlphaMode && ( + <button + disabled={this.state.entered.length === this.maxAllowedChars()} + className={classNames(styles.keypadButton, styles.keypadZeroButton)} + onTouchStart={() => this.addToEntry(0)} + onClick={() => { + if (!hasTouchEvents) this.addToEntry(0); + }} + > + 0 + </button> + )} <button - disabled={this.state.enteredDigits.length === 0 || this.state.enteredDigits.length === MAX_DIGITS} + disabled={this.state.entered.length === 0 || this.state.entered.length === this.maxAllowedChars()} className={classNames(styles.keypadButton, styles.keypadBackspace)} - onClick={() => this.removeDigit()} + onTouchStart={() => this.removeChar()} + onClick={() => { + if (!hasTouchEvents) this.removeChar(); + }} > ⌫ </button> </div> <div className={styles.footer}> - <span> - <FormattedMessage id="link.dont_have_a_code" /> - </span>{" "} - <span> - <a href="/"> - <FormattedMessage id="link.create_a_room" /> - </a> - </span> - <img className={styles.footerImage} src="../assets/images/logo.svg" alt="Logo" /> + <div + className={styles.linkHeadsetFooterLink} + style={{ visibility: this.state.isAlphaMode ? "hidden" : "visible" }} + > + <img onClick={() => this.toggleMode()} src={HeadsetIcon} className={styles.headsetIcon} /> + <span> + <a href="#" onClick={() => this.toggleMode()}> + <FormattedMessage id="link.linking_a_headset" /> + </a> + </span> + </div> </div> </div> </div> diff --git a/src/react-components/profile-entry-panel.js b/src/react-components/profile-entry-panel.js index 32a1223e8105ec4ba301b5c3422787c6b38be4eb..307ae73b0dac97d5b50908d5773f28bc2953aec1 100644 --- a/src/react-components/profile-entry-panel.js +++ b/src/react-components/profile-entry-panel.js @@ -2,6 +2,9 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import { injectIntl, FormattedMessage } from "react-intl"; import { SCHEMA } from "../storage/store"; +import styles from "../assets/stylesheets/profile.scss"; +import classNames from "classnames"; +import hubLogo from "../assets/images/hub-preview-white.png"; class ProfileEntryPanel extends Component { static propTypes = { @@ -72,27 +75,25 @@ class ProfileEntryPanel extends Component { const { formatMessage } = this.props.intl; return ( - <div className="profile-entry"> - <form onSubmit={this.saveStateAndFinish} className="profile-entry__form"> - <div className="profile-entry__box profile-entry__box--darkened"> - <label htmlFor="#profile-entry-display-name" className="profile-entry__subtitle"> + <div className={styles.profileEntry}> + <form onSubmit={this.saveStateAndFinish} className={styles.form}> + <div className={classNames([styles.box, styles.darkened])}> + <label htmlFor="#profile-entry-display-name" className={styles.title}> <FormattedMessage id="profile.header" /> </label> - <label> - <input - id="profile-entry-display-name" - className="profile-entry__form-field-text" - value={this.state.displayName} - onFocus={e => e.target.select()} - onChange={e => this.setState({ displayName: e.target.value })} - required - spellCheck="false" - pattern={SCHEMA.definitions.profile.properties.displayName.pattern} - title={formatMessage({ id: "profile.display_name.validation_warning" })} - ref={inp => (this.nameInput = inp)} - /> - </label> - <div className="profile-entry__avatar-selector-container"> + <input + id="profile-entry-display-name" + className={styles.formFieldText} + value={this.state.displayName} + onFocus={e => e.target.select()} + onChange={e => this.setState({ displayName: e.target.value })} + required + spellCheck="false" + pattern={SCHEMA.definitions.profile.properties.displayName.pattern} + title={formatMessage({ id: "profile.display_name.validation_warning" })} + ref={inp => (this.nameInput = inp)} + /> + <div className={styles.avatarSelectorContainer}> <div className="loading-panel"> <div className="loader-wrap"> <div className="loader"> @@ -101,13 +102,13 @@ class ProfileEntryPanel extends Component { </div> </div> <iframe - className="profile-entry__avatar-selector" + className={styles.avatarSelector} src={`/avatar-selector.html#avatar_id=${this.state.avatarId}`} ref={ifr => (this.avatarSelector = ifr)} /> </div> - <input className="profile-entry__form-submit" type="submit" value={formatMessage({ id: "profile.save" })} /> - <div className="profile-entry__box__links"> + <input className={styles.formSubmit} type="submit" value={formatMessage({ id: "profile.save" })} /> + <div className={styles.links}> <a target="_blank" rel="noopener noreferrer" href="https://github.com/mozilla/hubs/blob/master/TERMS.md"> <FormattedMessage id="profile.terms_of_use" /> </a> @@ -121,6 +122,7 @@ class ProfileEntryPanel extends Component { </div> </div> </form> + <img className={styles.logo} src={hubLogo} /> </div> ); } diff --git a/src/react-components/profile-info-header.js b/src/react-components/profile-info-header.js deleted file mode 100644 index 7afb16b5491c4ffccfbe39ed2674c821d28c0ed5..0000000000000000000000000000000000000000 --- a/src/react-components/profile-info-header.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faQuestion } from "@fortawesome/free-solid-svg-icons/faQuestion"; - -export const ProfileInfoHeader = props => ( - <div className="profile-info-header"> - <div className="profile-info-header__menu-buttons"> - <button className="profile-info-header__menu-buttons__menu-button" onClick={props.onClickHelp}> - <i className="profile-info-header__menu-buttons__menu-button__icon"> - <FontAwesomeIcon icon={faQuestion} /> - </i> - </button> - </div> - <div className="profile-info-header__profile_display_name"> - <img src="../assets/images/account.svg" onClick={props.onClickName} className="profile-info-header__icon" /> - <div onClick={props.onClickName} title={props.name}> - {props.name} - </div> - </div> - </div> -); - -ProfileInfoHeader.propTypes = { - onClickName: PropTypes.func, - onClickHelp: PropTypes.func, - name: PropTypes.string -}; diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index 347ddd7b9fec924ec3bd5e5af9a6ec439d7d21cf..e3a646a2ff6691b8777dce096a157466f84f2ea7 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -18,13 +18,12 @@ import { DaydreamEntryButton, SafariEntryButton } from "./entry-buttons.js"; -import { ProfileInfoHeader } from "./profile-info-header.js"; import ProfileEntryPanel from "./profile-entry-panel"; import HelpDialog from "./help-dialog.js"; import SafariDialog from "./safari-dialog.js"; import WebVRRecommendDialog from "./webvr-recommend-dialog.js"; -import InviteDialog from "./invite-dialog.js"; import InviteTeamDialog from "./invite-team-dialog.js"; +import InviteDialog from "./invite-dialog.js"; import LinkDialog from "./link-dialog.js"; import CreateObjectDialog from "./create-object-dialog.js"; import TwoDHUD from "./2d-hud"; @@ -32,11 +31,14 @@ import { faUsers } from "@fortawesome/free-solid-svg-icons/faUsers"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faQuestion } from "@fortawesome/free-solid-svg-icons/faQuestion"; +import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown"; +import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp"; addLocaleData([...en]); const ENTRY_STEPS = { start: "start", + device: "device", mic_grant: "mic_grant", mic_granted: "mic_granted", audio_setup: "audio_setup", @@ -73,9 +75,9 @@ class UIRoot extends Component { scene: PropTypes.object, hubChannel: PropTypes.object, linkChannel: PropTypes.object, - showProfileEntry: PropTypes.bool, + hubEntryCode: PropTypes.number, availableVREntryTypes: PropTypes.object, - initialEnvironmentLoaded: PropTypes.bool, + environmentSceneLoaded: PropTypes.bool, roomUnavailableReason: PropTypes.string, platformUnsupportedReason: PropTypes.string, hubId: PropTypes.string, @@ -88,6 +90,8 @@ class UIRoot extends Component { entryStep: ENTRY_STEPS.start, enterInVR: false, dialog: null, + showInviteDialog: false, + showLinkDialog: false, linkCode: null, linkCodeCancel: null, @@ -96,6 +100,7 @@ class UIRoot extends Component { mediaStream: null, videoTrack: null, audioTrack: null, + entryPanelCollapsed: false, toneInterval: null, tonePlaying: false, @@ -119,11 +124,6 @@ class UIRoot extends Component { showProfileEntry: false }; - constructor(props) { - super(props); - this.state.showProfileEntry = this.props.showProfileEntry; - } - componentDidMount() { this.props.concurrentLoadDetector.addEventListener("concurrentload", this.onConcurrentLoad); this.micLevelMovingAverage = MovingAverage(100); @@ -138,12 +138,6 @@ class UIRoot extends Component { this.props.scene.removeEventListener("exit", this.exit); } - componentDidUpdate(prevProps) { - if (this.props.availableVREntryTypes && prevProps.availableVREntryTypes !== this.props.availableVREntryTypes) { - this.handleForcedVREntryType(); - } - } - onSceneLoaded = () => { this.setState({ sceneLoaded: true }); }; @@ -172,10 +166,16 @@ class UIRoot extends Component { this.props.scene.emit("spawn_pen"); }; - handleForcedVREntryType = () => { - if (!this.props.forcedVREntryType) return; + handleStartEntry = () => { + const promptForNameAndAvatarBeforeEntry = !this.props.store.state.activity.hasChangedName; - if (this.props.forcedVREntryType.startsWith("daydream")) { + if (promptForNameAndAvatarBeforeEntry) { + this.setState({ showProfileEntry: true }); + } + + if (!this.props.forcedVREntryType) { + this.setState({ entryStep: ENTRY_STEPS.device }); + } else if (this.props.forcedVREntryType.startsWith("daydream")) { this.enterDaydream(); } else if (this.props.forcedVREntryType.startsWith("vr")) { this.enterVR(); @@ -285,23 +285,7 @@ class UIRoot extends Component { }; enterDaydream = async () => { - if (this.props.availableVREntryTypes.daydream == VR_DEVICE_AVAILABILITY.maybe) { - this.exit(); - - // We are not in mobile chrome, so launch into chrome via an Intent URL - const location = window.location; - const qs = new URLSearchParams(location.search); - qs.set("vr_entry_type", "daydream"); // Auto-choose 'daydream' after landing in chrome - - const intentUrl = - `intent://${location.host}${location.pathname}?` + - `${qs}#Intent;scheme=${location.protocol.replace(":", "")};` + - `action=android.intent.action.VIEW;package=com.android.chrome;end;`; - - window.location = intentUrl; - } else { - await this.performDirectEntryFlow(true); - } + await this.performDirectEntryFlow(true); }; micDeviceChanged = async ev => { @@ -516,29 +500,32 @@ class UIRoot extends Component { clearInterval(this.state.micUpdateInterval); } - this.setState({ entryStep: ENTRY_STEPS.finished }); + this.setState({ showInviteDialog: false, entryStep: ENTRY_STEPS.finished }); }; attemptLink = async () => { - this.showLinkDialog(); + this.setState({ showLinkDialog: true }); const { code, cancel, onFinished } = await this.props.linkChannel.generateCode(); this.setState({ linkCode: code, linkCodeCancel: cancel }); - this.showLinkDialog(); - onFinished.then(this.closeDialog); + onFinished.then(() => this.setState({ showLinkDialog: false, linkCode: null, linkCodeCancel: null })); }; - closeDialog = async () => { - if (this.state.linkCodeCancel) { - this.state.linkCodeCancel(); - } + showInviteDialog = () => { + this.setState({ showInviteDialog: true }); + }; - this.setState({ dialog: null, linkCode: null, linkCodeCancel: null }); + toggleInviteDialog = async () => { + this.setState({ showInviteDialog: !this.state.showInviteDialog }); }; createObject = media => { this.props.scene.emit("add_media", media); }; + closeDialog = () => { + this.setState({ dialog: null }); + }; + showHelpDialog() { this.setState({ dialog: <HelpDialog onClose={this.closeDialog} /> }); } @@ -547,10 +534,6 @@ class UIRoot extends Component { this.setState({ dialog: <SafariDialog onClose={this.closeDialog} /> }); } - showInviteDialog() { - this.setState({ dialog: <InviteDialog onClose={this.closeDialog} /> }); - } - showInviteTeamDialog() { this.setState({ dialog: <InviteTeamDialog hubChannel={this.props.hubChannel} onClose={this.closeDialog} /> }); } @@ -559,355 +542,443 @@ class UIRoot extends Component { this.setState({ dialog: <CreateObjectDialog onCreate={this.createObject} onClose={this.closeDialog} /> }); } - showLinkDialog() { - this.setState({ dialog: <LinkDialog linkCode={this.state.linkCode} onClose={this.closeDialog} /> }); - } - showWebVRRecommendDialog() { this.setState({ dialog: <WebVRRecommendDialog onClose={this.closeDialog} /> }); } - render() { - if (this.state.exited || this.props.roomUnavailableReason || this.props.platformUnsupportedReason) { - let subtitle = null; - if (this.props.roomUnavailableReason === "closed") { - // TODO i18n, due to links and markup - subtitle = ( - <div> - Sorry, this room is no longer available. - <p /> - A room may be closed if we receive reports that it violates our{" "} - <a target="_blank" rel="noreferrer noopener" href="https://github.com/mozilla/hubs/blob/master/TERMS.md"> - Terms of Use - </a>. - <br /> - If you have questions, contact us at <a href="mailto:hubs@mozilla.com">hubs@mozilla.com</a>. - <p /> - If you'd like to run your own server, hubs's source code is available on{" "} - <a href="https://github.com/mozilla/hubs">GitHub</a>. - </div> - ); - } else if (this.props.platformUnsupportedReason === "no_data_channels") { - // TODO i18n, due to links and markup - subtitle = ( - <div> - Your browser does not support{" "} - <a - href="https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createDataChannel#Browser_compatibility" - rel="noreferrer noopener" - > - WebRTC Data Channels - </a>, which is required to use Hubs.<br />If you"d like to use Hubs with Oculus or SteamVR, you can{" "} - <a href="https://www.mozilla.org/firefox" rel="noreferrer noopener"> - Download Firefox - </a>. - </div> - ); - } else { - const reason = this.props.roomUnavailableReason || this.props.platformUnsupportedReason; - const exitSubtitleId = `exit.subtitle.${this.state.exited ? "exited" : reason}`; - subtitle = ( - <div> - <FormattedMessage id={exitSubtitleId} /> - <p /> - {this.props.roomUnavailableReason && ( - <div> - You can also <a href="/">create a new room</a>. - </div> - )} - </div> - ); - } - - return ( - <IntlProvider locale={lang} messages={messages}> - <div className="exited-panel"> - <img className="exited-panel__logo" src="../assets/images/logo.svg" /> - <div className="exited-panel__subtitle">{subtitle}</div> - </div> - </IntlProvider> + renderExitedPane = () => { + let subtitle = null; + if (this.props.roomUnavailableReason === "closed") { + // TODO i18n, due to links and markup + subtitle = ( + <div> + Sorry, this room is no longer available. + <p /> + A room may be closed if we receive reports that it violates our{" "} + <a target="_blank" rel="noreferrer noopener" href="https://github.com/mozilla/hubs/blob/master/TERMS.md"> + Terms of Use + </a>. + <br /> + If you have questions, contact us at <a href="mailto:hubs@mozilla.com">hubs@mozilla.com</a>. + <p /> + If you'd like to run your own server, hubs's source code is available on{" "} + <a href="https://github.com/mozilla/hubs">GitHub</a>. + </div> ); - } - - if (this.props.isBotMode) { - return ( - <div className="loading-panel"> - <img className="loading-panel__logo" src="../assets/images/logo.svg" /> - <input type="file" id="bot-audio-input" accept="audio/*" /> - <input type="file" id="bot-data-input" accept="application/json" /> + } else if (this.props.platformUnsupportedReason === "no_data_channels") { + // TODO i18n, due to links and markup + subtitle = ( + <div> + Your browser does not support{" "} + <a + href="https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createDataChannel#Browser_compatibility" + rel="noreferrer noopener" + > + WebRTC Data Channels + </a>, which is required to use Hubs.<br />If you"d like to use Hubs with Oculus or SteamVR, you can{" "} + <a href="https://www.mozilla.org/firefox" rel="noreferrer noopener"> + Download Firefox + </a>. + </div> + ); + } else { + const reason = this.props.roomUnavailableReason || this.props.platformUnsupportedReason; + const exitSubtitleId = `exit.subtitle.${this.state.exited ? "exited" : reason}`; + subtitle = ( + <div> + <FormattedMessage id={exitSubtitleId} /> + <p /> + {this.props.roomUnavailableReason && ( + <div> + You can also <a href="/">create a new room</a>. + </div> + )} </div> ); } - if (!this.props.initialEnvironmentLoaded || !this.props.availableVREntryTypes || !this.props.hubId) { - return ( - <IntlProvider locale={lang} messages={messages}> - <div className="loading-panel"> - <div className="loader-wrap"> - <div className="loader"> - <div className="loader-center" /> - </div> - </div> + return ( + <IntlProvider locale={lang} messages={messages}> + <div className="exited-panel"> + <img className="exited-panel__logo" src="../assets/images/logo.svg" /> + <div className="exited-panel__subtitle">{subtitle}</div> + </div> + </IntlProvider> + ); + }; + + renderBotMode = () => { + return ( + <div className="loading-panel"> + <img className="loading-panel__logo" src="../assets/images/logo.svg" /> + <input type="file" id="bot-audio-input" accept="audio/*" /> + <input type="file" id="bot-data-input" accept="application/json" /> + </div> + ); + }; - <img className="loading-panel__logo" src="../assets/images/logo.svg" /> + renderLoader = () => { + return ( + <IntlProvider locale={lang} messages={messages}> + <div className="loading-panel"> + <div className="loader-wrap"> + <div className="loader"> + <div className="loader-center" /> + </div> </div> - </IntlProvider> - ); - } - // Only show this in desktop firefox since other browsers/platforms will ignore the "screen" media constraint and - // will attempt to share your webcam instead! - const screenSharingCheckbox = this.props.enableScreenSharing && - !AFRAME.utils.device.isMobile() && - /firefox/i.test(navigator.userAgent) && ( - <label className={entryStyles.screenSharing}> - <input - className={entryStyles.checkbox} - type="checkbox" - value={this.state.shareScreen} - onChange={this.setStateAndRequestScreen} - /> - <FormattedMessage id="entry.enable-screen-sharing" /> - </label> - ); + <img className="loading-panel__logo" src="../assets/images/hub-preview-light-no-shadow.png" /> + </div> + </IntlProvider> + ); + }; - const entryPanel = - this.state.entryStep === ENTRY_STEPS.start ? ( - <div className={entryStyles.entryPanel}> - <div className={entryStyles.buttonContainer}> - {false /* TODO */ && ( - <div className={entryStyles.presenceInfo}> - <span className={entryStyles.people}>2 people</span> have joined - </div> - )} - {this.props.availableVREntryTypes.screen === VR_DEVICE_AVAILABILITY.yes && ( - <TwoDEntryButton onClick={this.enter2D} /> - )} - {this.props.availableVREntryTypes.safari === VR_DEVICE_AVAILABILITY.maybe && ( - <SafariEntryButton onClick={this.showSafariDialog} /> - )} - {this.props.availableVREntryTypes.generic !== VR_DEVICE_AVAILABILITY.no && ( - <GenericEntryButton onClick={this.enterVR} /> - )} - {this.props.availableVREntryTypes.daydream !== VR_DEVICE_AVAILABILITY.no && ( - <DaydreamEntryButton - onClick={this.enterDaydream} - subtitle={ - this.props.availableVREntryTypes.daydream == VR_DEVICE_AVAILABILITY.maybe - ? "entry.daydream-via-chrome" - : null - } - /> - )} - <DeviceEntryButton onClick={this.attemptLink} isInHMD={this.props.availableVREntryTypes.isInHMD} /> - {this.props.availableVREntryTypes.cardboard !== VR_DEVICE_AVAILABILITY.no && ( - <div className={entryStyles.secondary} onClick={this.enterVR}> - <FormattedMessage id="entry.cardboard" /> - </div> - )} - {screenSharingCheckbox} - <button className={entryStyles.inviteButton} onClick={() => this.showInviteDialog()}> - <FormattedMessage id="entry.invite-others" /> - </button> + renderEntryStartPanel = () => { + return ( + <div className={entryStyles.entryPanel}> + <div className={entryStyles.title}>{this.props.hubName}</div> + + <div className={entryStyles.center}> + <div onClick={() => this.setState({ showProfileEntry: true })} className={entryStyles.profileName}> + <img src="../assets/images/account.svg" className={entryStyles.profileIcon} /> + <div title={this.props.store.state.profile.displayName}>{this.props.store.state.profile.displayName}</div> </div> </div> - ) : null; - const micPanel = - this.state.entryStep === ENTRY_STEPS.mic_grant || this.state.entryStep === ENTRY_STEPS.mic_granted ? ( - <div className="mic-grant-panel"> - <div className="mic-grant-panel__grant-container"> - <div className="mic-grant-panel__title"> - <FormattedMessage - id={this.state.entryStep == ENTRY_STEPS.mic_grant ? "audio.grant-title" : "audio.granted-title"} - /> - </div> - <div className="mic-grant-panel__subtitle"> - <FormattedMessage - id={this.state.entryStep == ENTRY_STEPS.mic_grant ? "audio.grant-subtitle" : "audio.granted-subtitle"} - /> - </div> - <div className="mic-grant-panel__button-container"> - {this.state.entryStep == ENTRY_STEPS.mic_grant ? ( - <button className="mic-grant-panel__button" onClick={this.onMicGrantButton}> - <img src="../assets/images/mic_denied.png" srcSet="../assets/images/mic_denied@2x.png 2x" /> - </button> - ) : ( - <button className="mic-grant-panel__button" onClick={this.onMicGrantButton}> - <img src="../assets/images/mic_granted.png" srcSet="../assets/images/mic_granted@2x.png 2x" /> - </button> - )} + <div className={entryStyles.buttonContainer}> + <button + className={classNames([entryStyles.actionButton, entryStyles.wideButton])} + onClick={() => this.handleStartEntry()} + > + <FormattedMessage id="entry.enter-room" /> + </button> + </div> + </div> + ); + }; + + renderDevicePanel = () => { + // Only screen sharing in desktop firefox since other browsers/platforms will ignore the "screen" media constraint and will attempt to share your webcam instead! + const isFireFox = /firefox/i.test(navigator.userAgent); + const isNonMobile = !AFRAME.utils.device.isMobile(); + + const screenSharingCheckbox = + this.props.enableScreenSharing && isNonMobile && isFireFox && this.renderScreensharing(); + + return ( + <div className={entryStyles.entryPanel}> + <div className={entryStyles.title}> + <FormattedMessage id="entry.choose-device" /> + </div> + + <div className={entryStyles.buttonContainer}> + {this.props.availableVREntryTypes.screen === VR_DEVICE_AVAILABILITY.yes && ( + <TwoDEntryButton onClick={this.enter2D} /> + )} + {this.props.availableVREntryTypes.safari === VR_DEVICE_AVAILABILITY.maybe && ( + <SafariEntryButton onClick={this.showSafariDialog} /> + )} + {this.props.availableVREntryTypes.generic !== VR_DEVICE_AVAILABILITY.no && ( + <GenericEntryButton onClick={this.enterVR} /> + )} + {this.props.availableVREntryTypes.daydream === VR_DEVICE_AVAILABILITY.yes && ( + <DaydreamEntryButton onClick={this.enterDaydream} subtitle={null} /> + )} + <DeviceEntryButton onClick={() => this.attemptLink()} isInHMD={this.props.availableVREntryTypes.isInHMD} /> + {this.props.availableVREntryTypes.cardboard !== VR_DEVICE_AVAILABILITY.no && ( + <div className={entryStyles.secondary} onClick={this.enterVR}> + <FormattedMessage id="entry.cardboard" /> </div> + )} + {screenSharingCheckbox} + </div> + </div> + ); + }; + + renderScreensharing = () => { + return ( + <label className={entryStyles.screenSharing}> + <input + className={entryStyles.checkbox} + type="checkbox" + value={this.state.shareScreen} + onChange={this.setStateAndRequestScreen} + /> + <FormattedMessage id="entry.enable-screen-sharing" /> + </label> + ); + }; + + renderMicPanel = () => { + return ( + <div className="mic-grant-panel"> + <div className="mic-grant-panel__grant-container"> + <div className="mic-grant-panel__title"> + <FormattedMessage + id={this.state.entryStep == ENTRY_STEPS.mic_grant ? "audio.grant-title" : "audio.granted-title"} + /> </div> - <div className="mic-grant-panel__next-container"> - <button className={classNames("mic-grant-panel__next")} onClick={this.onMicGrantButton}> - <FormattedMessage id="audio.granted-next" /> - </button> + <div className="mic-grant-panel__subtitle"> + <FormattedMessage + id={this.state.entryStep == ENTRY_STEPS.mic_grant ? "audio.grant-subtitle" : "audio.granted-subtitle"} + /> + </div> + <div className="mic-grant-panel__button-container"> + {this.state.entryStep == ENTRY_STEPS.mic_grant ? ( + <button className="mic-grant-panel__button" onClick={this.onMicGrantButton}> + <img src="../assets/images/mic_denied.png" srcSet="../assets/images/mic_denied@2x.png 2x" /> + </button> + ) : ( + <button className="mic-grant-panel__button" onClick={this.onMicGrantButton}> + <img src="../assets/images/mic_granted.png" srcSet="../assets/images/mic_granted@2x.png 2x" /> + </button> + )} </div> </div> - ) : null; + <div className="mic-grant-panel__next-container"> + <button className={classNames("mic-grant-panel__next")} onClick={this.onMicGrantButton}> + <FormattedMessage id="audio.granted-next" /> + </button> + </div> + </div> + ); + }; + renderAudioSetupPanel = () => { const maxLevelHeight = 111; const micClip = { clip: `rect(${maxLevelHeight - Math.floor(this.state.micLevel * maxLevelHeight)}px, 111px, 111px, 0px)` }; const speakerClip = { clip: `rect(${this.state.tonePlaying ? 0 : maxLevelHeight}px, 111px, 111px, 0px)` }; const subtitleId = AFRAME.utils.device.isMobile() ? "audio.subtitle-mobile" : "audio.subtitle-desktop"; - const audioSetupPanel = - this.state.entryStep === ENTRY_STEPS.audio_setup ? ( - <div className="audio-setup-panel"> - <div> - <div className="audio-setup-panel__title"> - <FormattedMessage id="audio.title" /> - </div> - <div className="audio-setup-panel__subtitle"> - {(AFRAME.utils.device.isMobile() || this.state.enterInVR) && <FormattedMessage id={subtitleId} />} - </div> - <div className="audio-setup-panel__levels"> - <div className="audio-setup-panel__levels__icon"> - <img - src="../assets/images/level_background.png" - srcSet="../assets/images/level_background@2x.png 2x" - className="audio-setup-panel__levels__icon-part" - /> - <img - src="../assets/images/level_fill.png" - srcSet="../assets/images/level_fill@2x.png 2x" - className="audio-setup-panel__levels__icon-part" - style={micClip} - /> - {this.state.audioTrack ? ( - <img - src="../assets/images/mic_level.png" - srcSet="../assets/images/mic_level@2x.png 2x" - className="audio-setup-panel__levels__icon-part" - /> - ) : ( - <img - src="../assets/images/mic_denied.png" - srcSet="../assets/images/mic_denied@2x.png 2x" - className="audio-setup-panel__levels__icon-part" - /> - )} - </div> - <div className="audio-setup-panel__levels__icon" onClick={this.playTestTone}> - <img - src="../assets/images/level_background.png" - srcSet="../assets/images/level_background@2x.png 2x" - className="audio-setup-panel__levels__icon-part" - /> + return ( + <div className="audio-setup-panel"> + <div> + <div className="audio-setup-panel__title"> + <FormattedMessage id="audio.title" /> + </div> + <div className="audio-setup-panel__subtitle"> + {(AFRAME.utils.device.isMobile() || this.state.enterInVR) && <FormattedMessage id={subtitleId} />} + </div> + <div className="audio-setup-panel__levels"> + <div className="audio-setup-panel__levels__icon"> + <img + src="../assets/images/level_background.png" + srcSet="../assets/images/level_background@2x.png 2x" + className="audio-setup-panel__levels__icon-part" + /> + <img + src="../assets/images/level_fill.png" + srcSet="../assets/images/level_fill@2x.png 2x" + className="audio-setup-panel__levels__icon-part" + style={micClip} + /> + {this.state.audioTrack ? ( <img - src="../assets/images/level_fill.png" - srcSet="../assets/images/level_fill@2x.png 2x" + src="../assets/images/mic_level.png" + srcSet="../assets/images/mic_level@2x.png 2x" className="audio-setup-panel__levels__icon-part" - style={speakerClip} /> + ) : ( <img - src="../assets/images/speaker_level.png" - srcSet="../assets/images/speaker_level@2x.png 2x" + src="../assets/images/mic_denied.png" + srcSet="../assets/images/mic_denied@2x.png 2x" className="audio-setup-panel__levels__icon-part" /> - </div> + )} + </div> + <div className="audio-setup-panel__levels__icon" onClick={this.playTestTone}> + <img + src="../assets/images/level_background.png" + srcSet="../assets/images/level_background@2x.png 2x" + className="audio-setup-panel__levels__icon-part" + /> + <img + src="../assets/images/level_fill.png" + srcSet="../assets/images/level_fill@2x.png 2x" + className="audio-setup-panel__levels__icon-part" + style={speakerClip} + /> + <img + src="../assets/images/speaker_level.png" + srcSet="../assets/images/speaker_level@2x.png 2x" + className="audio-setup-panel__levels__icon-part" + /> </div> - {this.state.audioTrack && ( - <div className="audio-setup-panel__device-chooser"> - <select - className="audio-setup-panel__device-chooser__dropdown" - value={this.selectedMicDeviceId()} - onChange={this.micDeviceChanged} - > - {this.state.micDevices.map(d => ( - <option key={d.deviceId} value={d.deviceId}> - {d.label} - </option> - ))} - </select> - <img - className="audio-setup-panel__device-chooser__mic-icon" - src="../assets/images/mic_small.png" - srcSet="../assets/images/mic_small@2x.png 2x" - /> - <img - className="audio-setup-panel__device-chooser__dropdown-arrow" - src="../assets/images/dropdown_arrow.png" - srcSet="../assets/images/dropdown_arrow@2x.png 2x" - /> - </div> - )} - {this.shouldShowHmdMicWarning() && ( - <div className="audio-setup-panel__hmd-mic-warning"> - <img - src="../assets/images/warning_icon.png" - srcSet="../assets/images/warning_icon@2x.png 2x" - className="audio-setup-panel__hmd-mic-warning__icon" - /> - <span className="audio-setup-panel__hmd-mic-warning__label"> - <FormattedMessage id="audio.hmd-mic-warning" /> - </span> - </div> - )} - </div> - <div className="audio-setup-panel__enter-button-container"> - <button className="audio-setup-panel__enter-button" onClick={this.onAudioReadyButton}> - <FormattedMessage id="audio.enter-now" /> - </button> </div> + {this.state.audioTrack && ( + <div className="audio-setup-panel__device-chooser"> + <select + className="audio-setup-panel__device-chooser__dropdown" + value={this.selectedMicDeviceId()} + onChange={this.micDeviceChanged} + > + {this.state.micDevices.map(d => ( + <option key={d.deviceId} value={d.deviceId}> + {d.label} + </option> + ))} + </select> + <img + className="audio-setup-panel__device-chooser__mic-icon" + src="../assets/images/mic_small.png" + srcSet="../assets/images/mic_small@2x.png 2x" + /> + <img + className="audio-setup-panel__device-chooser__dropdown-arrow" + src="../assets/images/dropdown_arrow.png" + srcSet="../assets/images/dropdown_arrow@2x.png 2x" + /> + </div> + )} + {this.shouldShowHmdMicWarning() && ( + <div className="audio-setup-panel__hmd-mic-warning"> + <img + src="../assets/images/warning_icon.png" + srcSet="../assets/images/warning_icon@2x.png 2x" + className="audio-setup-panel__hmd-mic-warning__icon" + /> + <span className="audio-setup-panel__hmd-mic-warning__label"> + <FormattedMessage id="audio.hmd-mic-warning" /> + </span> + </div> + )} + </div> + <div className="audio-setup-panel__enter-button-container"> + <button className="audio-setup-panel__enter-button" onClick={this.onAudioReadyButton}> + <FormattedMessage id="audio.enter-now" /> + </button> </div> - ) : null; - - const dialogContents = this.isWaitingForAutoExit() ? ( - <AutoExitWarning secondsRemaining={this.state.secondsRemainingBeforeAutoExit} onCancel={this.endAutoExitTimer} /> - ) : ( - <div className={entryStyles.entryDialog}> - <ProfileInfoHeader - name={this.props.store.state.profile.displayName} - onClickName={() => this.setState({ showProfileEntry: true })} - onClickHelp={() => this.showHelpDialog()} - /> - {entryPanel} - {micPanel} - {audioSetupPanel} </div> ); + }; + + render() { + const isExited = this.state.exited || this.props.roomUnavailableReason || this.props.platformUnsupportedReason; + const isLoading = !this.props.environmentSceneLoaded || !this.props.availableVREntryTypes || !this.props.hubId; - const dialogBoxClassNames = classNames({ "ui-interactive": !this.state.dialog, "ui-dialog-box": true }); + if (isExited) return this.renderExitedPane(); + if (isLoading) return this.renderLoader(); + if (this.props.isBotMode) return this.renderBotMode(); + + const startPanel = this.state.entryStep === ENTRY_STEPS.start && this.renderEntryStartPanel(); + const devicePanel = this.state.entryStep === ENTRY_STEPS.device && this.renderDevicePanel(); + + const micPanel = + (this.state.entryStep === ENTRY_STEPS.mic_grant || this.state.entryStep === ENTRY_STEPS.mic_granted) && + this.renderMicPanel(); + + const audioSetupPanel = this.state.entryStep === ENTRY_STEPS.audio_setup && this.renderAudioSetupPanel(); + + // Dialog is empty if coll + let dialogContents = null; + + if (this.state.entryPanelCollapsed && !this.isWaitingForAutoExit()) { + dialogContents = ( + <div className={entryStyles.entryDialog}> + <div> </div> + <button onClick={() => this.setState({ entryPanelCollapsed: false })} className={entryStyles.expand}> + <i> + <FontAwesomeIcon icon={faChevronUp} /> + </i> + </button> + </div> + ); + } else { + dialogContents = this.isWaitingForAutoExit() ? ( + <AutoExitWarning + secondsRemaining={this.state.secondsRemainingBeforeAutoExit} + onCancel={this.endAutoExitTimer} + /> + ) : ( + <div className={entryStyles.entryDialog}> + {!this.state.entryPanelCollapsed && ( + <button onClick={() => this.setState({ entryPanelCollapsed: true })} className={entryStyles.collapse}> + <i> + <FontAwesomeIcon icon={faChevronDown} /> + </i> + </button> + )} + {startPanel} + {devicePanel} + {micPanel} + {audioSetupPanel} + </div> + ); + } const dialogBoxContentsClassNames = classNames({ - "ui-dialog-box-contents": true, - "ui-dialog-box-contents--backgrounded": this.state.showProfileEntry + [styles.uiInteractive]: !this.state.dialog, + [styles.uiDialogBoxContents]: true, + [styles.backgrounded]: this.state.showProfileEntry }); + const entryFinished = this.state.entryStep === ENTRY_STEPS.finished; + return ( <IntlProvider locale={lang} messages={messages}> - <div className="ui"> + <div className={styles.ui}> {this.state.dialog} - {this.state.entryStep === ENTRY_STEPS.finished && ( - <button onClick={() => this.showHelpDialog()} className="ui__help-icon"> - <i className="ui__help-icon__icon"> - <FontAwesomeIcon icon={faQuestion} /> - </i> - </button> + {this.state.showProfileEntry && ( + <ProfileEntryPanel finished={this.onProfileFinished} store={this.props.store} /> )} - {this.state.entryStep === ENTRY_STEPS.finished && ( - <div className={styles.presenceInfo}> - <FontAwesomeIcon icon={faUsers} /> - <span className={styles.occupantCount}>{this.props.occupantCount || "-"}</span> + {(!entryFinished || this.isWaitingForAutoExit()) && ( + <div className={styles.uiDialog}> + <div className={dialogBoxContentsClassNames}>{dialogContents}</div> </div> )} - <div className="ui-dialog"> - {(this.state.entryStep !== ENTRY_STEPS.finished || this.isWaitingForAutoExit()) && ( - <div className={dialogBoxClassNames}> - <div className={dialogBoxContentsClassNames}>{dialogContents}</div> - - {this.state.showProfileEntry && ( - <ProfileEntryPanel finished={this.onProfileFinished} store={this.props.store} /> - )} - </div> + <div + className={classNames({ + [styles.inviteContainer]: true, + [styles.inviteContainerBelowHud]: entryFinished, + [styles.inviteContainerInverted]: this.state.showInviteDialog + })} + > + {(!entryFinished || (this.props.occupantCount <= 1 && !this.props.availableVREntryTypes.isInHMD)) && ( + <button onClick={() => this.toggleInviteDialog()}> + <FormattedMessage id="entry.invite-others-nag" /> + </button> + )} + {this.props.availableVREntryTypes.isInHMD && + entryFinished && ( + <button onClick={() => this.props.scene.enterVR()}> + <FormattedMessage id="entry.return-to-vr" /> + </button> + )} + {this.state.showInviteDialog && ( + <InviteDialog + allowShare={!this.props.availableVREntryTypes.isInHMD} + entryCode={this.props.hubEntryCode} + onClose={() => this.setState({ showInviteDialog: false })} + /> )} </div> + + {this.state.showLinkDialog && ( + <LinkDialog + linkCode={this.state.linkCode} + onClose={() => { + this.state.linkCodeCancel(); + this.setState({ showLinkDialog: false, linkCode: null, linkCodeCancel: null }); + }} + /> + )} + + <button onClick={() => this.showHelpDialog()} className={styles.helpIcon}> + <i> + <FontAwesomeIcon icon={faQuestion} /> + </i> + </button> + + <div className={styles.presenceInfo}> + <FontAwesomeIcon icon={faUsers} /> + <span className={styles.occupantCount}>{this.props.occupantCount || "-"}</span> + </div> + {this.state.entryStep === ENTRY_STEPS.finished ? ( <div> <TwoDHUD.TopHUD @@ -920,21 +991,6 @@ class UIRoot extends Component { onSpawnPen={this.spawnPen} onSpawnCamera={() => this.props.scene.emit("action_spawn_camera")} /> - {!this.props.availableVREntryTypes.isInHMD && - this.props.occupantCount <= 1 && ( - <div className={styles.nagButton}> - <button onClick={() => this.showInviteDialog()}> - <FormattedMessage id="entry.invite-others-nag" /> - </button> - </div> - )} - {this.props.availableVREntryTypes.isInHMD && ( - <div className={styles.nagButton}> - <button onClick={() => this.props.scene.enterVR()}> - <FormattedMessage id="entry.return-to-vr" /> - </button> - </div> - )} {this.props.isSupportAvailable && ( <div className={styles.nagCornerButton}> <button onClick={() => this.showInviteTeamDialog()}> @@ -942,11 +998,13 @@ class UIRoot extends Component { </button> </div> )} - <TwoDHUD.BottomHUD - onCreateObject={() => this.showCreateObjectDialog()} - showPhotoPicker={AFRAME.utils.device.isMobile()} - onMediaPicked={this.createObject} - /> + {!this.isWaitingForAutoExit() && ( + <TwoDHUD.BottomHUD + onCreateObject={() => this.showCreateObjectDialog()} + showPhotoPicker={AFRAME.utils.device.isMobile()} + onMediaPicked={this.createObject} + /> + )} </div> ) : null} </div> diff --git a/src/react-components/webvr-recommend-dialog.js b/src/react-components/webvr-recommend-dialog.js index cd7d9434498a16ac219686d8fb5c264a962abd9e..264245b273f69e3ba6c17a893362d3f0f715c2a7 100644 --- a/src/react-components/webvr-recommend-dialog.js +++ b/src/react-components/webvr-recommend-dialog.js @@ -5,7 +5,7 @@ export default class WebVRRecommendDialog extends Component { render() { return ( <DialogContainer title="Enter in VR" {...this.props}> - <div> + <div style={{ display: "flex", flexDirection: "column", alignItems: "center" }}> <p>To enter Hubs with Oculus or SteamVR, you can use Firefox.</p> <a className="info-dialog--action-button" href="https://www.mozilla.org/firefox"> Download Firefox diff --git a/src/scene-entry-manager.js b/src/scene-entry-manager.js new file mode 100644 index 0000000000000000000000000000000000000000..f5b65c1fdb3234391cae1220c34a574293c0a742 --- /dev/null +++ b/src/scene-entry-manager.js @@ -0,0 +1,282 @@ +import qsTruthy from "./utils/qs_truthy"; +import screenfull from "screenfull"; +import { inGameActions } from "./input-mappings"; + +const playerHeight = 1.6; +const isBotMode = qsTruthy("bot"); +const isMobile = AFRAME.utils.device.isMobile(); +const isDebug = qsTruthy("debug"); +const qs = new URLSearchParams(location.search); +const aframeInspectorUrl = require("file-loader?name=assets/js/[name]-[hash].[ext]!aframe-inspector/dist/aframe-inspector.min.js"); + +import { addMedia } from "./utils/media-utils"; +import { ObjectContentOrigins } from "./object-types"; + +function requestFullscreen() { + if (screenfull.enabled && !screenfull.isFullscreen) screenfull.request(); +} + +export default class SceneEntryManager { + constructor(hubChannel) { + this.hubChannel = hubChannel; + this.store = window.APP.store; + this.scene = document.querySelector("a-scene"); + this.cursorController = document.querySelector("#cursor-controller"); + this.playerRig = document.querySelector("#player-rig"); + } + + init = () => { + this.whenSceneLoaded(() => { + this.cursorController.components["cursor-controller"].disable(); + }); + }; + + enterScene = async (mediaStream, enterInVR) => { + const playerCamera = document.querySelector("#player-camera"); + playerCamera.removeAttribute("scene-preview-camera"); + playerCamera.object3D.position.set(0, playerHeight, 0); + + // Get aframe inspector url using the webpack file-loader. + // Set the aframe-inspector url to our hosted copy. + this.scene.setAttribute("inspector", { url: aframeInspectorUrl }); + + if (isDebug) { + NAF.connection.adapter.session.options.verbose = true; + } + + if (enterInVR) { + this.scene.enterVR(); + } else if (AFRAME.utils.device.isMobile()) { + document.body.addEventListener("touchend", requestFullscreen); + } + + AFRAME.registerInputActions(inGameActions, "default"); + + if (isMobile || qsTruthy("mobile")) { + this.playerRig.setAttribute("virtual-gamepad-controls", {}); + } + + this._setupPlayerRig(); + this._setupScreensharing(mediaStream); + this._setupBlocking(); + this._setupMedia(); + this._setupCamera(); + + if (qsTruthy("offline")) return; + + if (mediaStream) { + NAF.connection.adapter.setLocalMediaStream(mediaStream); + } + + this._spawnAvatar(); + + if (isBotMode) { + this._runBot(mediaStream); + return; + } + + this.scene.classList.remove("hand-cursor"); + this.scene.classList.add("no-cursor"); + + const cursor = this.cursorController.components["cursor-controller"]; + cursor.enable(); + cursor.setCursorVisibility(true); + + this.hubChannel.sendEntryEvent().then(() => { + this.store.update({ activity: { lastEnteredAt: new Date().toISOString() } }); + }); + }; + + whenSceneLoaded = callback => { + if (this.scene.hasLoaded) { + callback(); + } else { + this.scene.addEventListener("loaded", callback); + } + }; + + enterSceneWhenLoaded = (mediaStream, enterInVR) => { + this.whenSceneLoaded(() => this.enterScene(mediaStream, enterInVR)); + }; + + exitScene = () => { + if (NAF.connection.adapter && NAF.connection.adapter.localMediaStream) { + NAF.connection.adapter.localMediaStream.getTracks().forEach(t => t.stop()); + } + if (this.hubChannel) { + this.hubChannel.disconnect(); + } + if (this.scene.renderer) { + this.scene.renderer.setAnimationLoop(null); // Stop animation loop, TODO A-Frame should do this + } + document.body.removeChild(this.scene); + document.body.removeEventListener("touchend", requestFullscreen); + }; + + _setupPlayerRig = () => { + this._updatePlayerRigWithProfile(); + this.store.addEventListener("statechanged", this._updatePlayerRigWithProfile); + + const avatarScale = parseInt(qs.get("avatar_scale"), 10); + + if (avatarScale) { + this.playerRig.setAttribute("scale", { x: avatarScale, y: avatarScale, z: avatarScale }); + } + }; + + _updatePlayerRigWithProfile = () => { + const displayName = this.store.state.profile.displayName; + this.playerRig.setAttribute("player-info", { + displayName, + avatarSrc: "#" + (this.store.state.profile.avatarId || "botdefault") + }); + const hudController = this.playerRig.querySelector("[hud-controller]"); + hudController.setAttribute("hud-controller", { showTip: !this.store.state.activity.hasFoundFreeze }); + this.scene.emit("username-changed", { username: displayName }); + }; + + _setupScreensharing = mediaStream => { + const videoTracks = mediaStream ? mediaStream.getVideoTracks() : []; + let sharingScreen = videoTracks.length > 0; + + const screenEntityId = `${NAF.clientId}-screen`; + let screenEntity = document.getElementById(screenEntityId); + + if (screenEntity) { + screenEntity.setAttribute("visible", sharingScreen); + } else if (sharingScreen) { + screenEntity = document.createElement("a-entity"); + screenEntity.id = screenEntityId; + screenEntity.setAttribute("offset-relative-to", { + target: "#player-camera", + offset: "0 0 -2", + on: "action_share_screen" + }); + screenEntity.setAttribute("networked", { template: "#video-template" }); + this.scene.appendChild(screenEntity); + } + + this.scene.addEventListener("action_share_screen", () => { + sharingScreen = !sharingScreen; + if (sharingScreen) { + for (const track of videoTracks) { + mediaStream.addTrack(track); + } + } else { + for (const track of mediaStream.getVideoTracks()) { + mediaStream.removeTrack(track); + } + } + NAF.connection.adapter.setLocalMediaStream(mediaStream); + screenEntity.setAttribute("visible", sharingScreen); + }); + }; + + _setupBlocking = () => { + document.body.addEventListener("blocked", ev => { + NAF.connection.entities.removeEntitiesOfClient(ev.detail.clientId); + }); + + document.body.addEventListener("unblocked", ev => { + NAF.connection.entities.completeSync(ev.detail.clientId); + }); + }; + + _setupMedia = () => { + const offset = { x: 0, y: 0, z: -1.5 }; + const spawnMediaInfrontOfPlayer = (src, contentOrigin) => { + const { entity, orientation } = addMedia(src, "#interactable-media", contentOrigin, true); + + orientation.then(or => { + entity.setAttribute("offset-relative-to", { + target: "#player-camera", + offset, + orientation: or + }); + }); + }; + + this.scene.addEventListener("add_media", e => { + const contentOrigin = e.detail instanceof File ? ObjectContentOrigins.FILE : ObjectContentOrigins.URL; + + spawnMediaInfrontOfPlayer(e.detail, contentOrigin); + }); + + this.scene.addEventListener("object_spawned", e => { + this.hubChannel.sendObjectSpawnedEvent(e.detail.objectType); + }); + + document.addEventListener("paste", e => { + if (e.target.nodeName === "INPUT") return; + + const url = e.clipboardData.getData("text"); + const files = e.clipboardData.files && e.clipboardData.files; + if (url) { + spawnMediaInfrontOfPlayer(url, ObjectContentOrigins.URL); + } else { + for (const file of files) { + spawnMediaInfrontOfPlayer(file, ObjectContentOrigins.CLIPBOARD); + } + } + }); + + document.addEventListener("dragover", e => e.preventDefault()); + + document.addEventListener("drop", e => { + e.preventDefault(); + const url = e.dataTransfer.getData("url"); + const files = e.dataTransfer.files; + if (url) { + spawnMediaInfrontOfPlayer(url, ObjectContentOrigins.URL); + } else { + for (const file of files) { + spawnMediaInfrontOfPlayer(file, ObjectContentOrigins.FILE); + } + } + }); + }; + + _setupCamera = () => { + this.scene.addEventListener("action_spawn_camera", () => { + const entity = document.createElement("a-entity"); + entity.setAttribute("networked", { template: "#interactable-camera" }); + entity.setAttribute("offset-relative-to", { + target: "#player-camera", + offset: { x: 0, y: 0, z: -1.5 } + }); + this.scene.appendChild(entity); + }); + }; + + _spawnAvatar = () => { + this.playerRig.setAttribute("networked", "template: #remote-avatar-template; attachTemplateToLocal: false;"); + this.playerRig.setAttribute("networked-avatar", ""); + this.playerRig.emit("entered"); + }; + + _runBot = async mediaStream => { + this.playerRig.setAttribute("avatar-replay", { + camera: "#player-camera", + leftController: "#player-left-controller", + rightController: "#player-right-controller" + }); + + const audioEl = document.createElement("audio"); + const audioInput = document.querySelector("#bot-audio-input"); + audioInput.onchange = () => { + audioEl.loop = true; + audioEl.muted = true; + audioEl.crossorigin = "anonymous"; + audioEl.src = URL.createObjectURL(audioInput.files[0]); + document.body.appendChild(audioEl); + }; + const dataInput = document.querySelector("#bot-data-input"); + dataInput.onchange = () => { + const url = URL.createObjectURL(dataInput.files[0]); + this.playerRig.setAttribute("avatar-replay", { recordingUrl: url }); + }; + await new Promise(resolve => audioEl.addEventListener("canplay", resolve)); + mediaStream.addTrack(audioEl.captureStream().getAudioTracks()[0]); + audioEl.play(); + }; +} diff --git a/src/scene.html b/src/scene.html index 222fa5d3be5878784e0612cc92c3717624a40036..1cfeab1b7d46cd42b71edd2af6e0db0307a9af32 100644 --- a/src/scene.html +++ b/src/scene.html @@ -9,7 +9,7 @@ <link rel="shortcut icon" type="image/png" href="/favicon.ico"> <title>Scene | Hubs by Mozilla</title> - <link href="https://fonts.googleapis.com/css?family=Zilla+Slab:300,300i,400,400i,700" rel="stylesheet"> + <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,700" rel="stylesheet"> </head> <body> diff --git a/src/utils/link-channel.js b/src/utils/link-channel.js index b4bac16957b51e1a9a6360601e223b9cca6b4a6b..172a327ade77fd434f2c70612c14ca9298cfb444 100644 --- a/src/utils/link-channel.js +++ b/src/utils/link-channel.js @@ -24,9 +24,8 @@ export default class LinkChannel { return new Promise(resolve => { const onFinished = new Promise(finished => { const step = () => { - const code = Math.floor(Math.random() * 9999) - .toString() - .padStart(4, "0"); + const getLetter = () => "ABCDEFGHI"[Math.floor(Math.random() * 9)]; + const code = `${getLetter()}${getLetter()}${getLetter()}${getLetter()}`; // Only respond to one link_request in this channel. let readyToSend = false; diff --git a/src/utils/next-tick.js b/src/utils/next-tick.js new file mode 100644 index 0000000000000000000000000000000000000000..85f66b7121e9245a6b9349ba0b8e9260a60ea057 --- /dev/null +++ b/src/utils/next-tick.js @@ -0,0 +1,5 @@ +export default function nextTick() { + return new Promise(resolve => { + setTimeout(resolve, 0); + }); +}