Skip to content

Commit

Permalink
Merge pull request #6092 from mozilla/ecs-pin-permissions
Browse files Browse the repository at this point in the history
Pinning/Unpinning interaction and media promotion checks for bitECS + entity state API
  • Loading branch information
johnshaughnessy authored Jul 17, 2023
2 parents 357492e + 3769682 commit d52bd00
Show file tree
Hide file tree
Showing 19 changed files with 230 additions and 125 deletions.
2 changes: 1 addition & 1 deletion .defaults.env
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ THUMBNAIL_SERVER="nearspark-dev.reticulum.io"
ASSET_BUNDLE_SERVER="https://asset-bundles-prod.reticulum.io"

# Comma-separated list of domains which are known to not need CORS proxying
NON_CORS_PROXY_DOMAINS="hubs.local,dev.reticulum.io,hubs-upload-cdn.com"
NON_CORS_PROXY_DOMAINS="hubs.local,hubs-proxy.local,dev.reticulum.io,hubs-upload-cdn.com"

# The root URL under which Hubs expects static assets to be served.
BASE_ASSETS_PATH=/
Expand Down
6 changes: 3 additions & 3 deletions src/bit-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,6 @@ export const PhysicsShape = defineComponent({
heightfieldDistance: Types.f32,
flags: Types.ui8
});
export const Pinnable = defineComponent();
export const Pinned = defineComponent();
export const DestroyAtExtremeDistance = defineComponent();
export const MediaLoading = defineComponent();
export const FloatyObject = defineComponent({ flags: Types.ui8, releaseGravity: Types.f32 });
Expand Down Expand Up @@ -153,9 +151,11 @@ export const CameraTool = defineComponent({
export const MyCameraTool = defineComponent();
export const MediaLoader = defineComponent({
src: Types.ui32,
flags: Types.ui8
flags: Types.ui8,
fileId: Types.ui32
});
MediaLoader.src[$isStringType] = true;
MediaLoader.fileId[$isStringType] = true;
export const MediaLoaded = defineComponent();
export const MediaContentBounds = defineComponent({
bounds: [Types.f32, 3]
Expand Down
24 changes: 10 additions & 14 deletions src/bit-systems/media-loading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,19 @@ export function* waitForMediaLoaded(world: HubsWorld, eid: EntityID) {
}
}

// prettier-ignore
const loaderForMediaType = {
[MediaType.IMAGE]: (
world: HubsWorld,
{ accessibleUrl, contentType }: { accessibleUrl: string; contentType: string }
) => loadImage(world, accessibleUrl, contentType),
[MediaType.VIDEO]: (
world: HubsWorld,
{ accessibleUrl, contentType }: { accessibleUrl: string; contentType: string }
) => loadVideo(world, accessibleUrl, contentType),
[MediaType.MODEL]: (
world: HubsWorld,
{ accessibleUrl, contentType }: { accessibleUrl: string; contentType: string }
) => loadModel(world, accessibleUrl, contentType, true),
[MediaType.PDF]: (world: HubsWorld, { accessibleUrl }: { accessibleUrl: string }) => loadPDF(world, accessibleUrl),
[MediaType.IMAGE]: (world: HubsWorld, { accessibleUrl, contentType }: { accessibleUrl: string, contentType: string }) =>
loadImage(world, accessibleUrl, contentType),
[MediaType.VIDEO]: (world: HubsWorld, { accessibleUrl, contentType }: { accessibleUrl: string, contentType: string }) =>
loadVideo(world, accessibleUrl, contentType),
[MediaType.MODEL]: (world: HubsWorld, { accessibleUrl, contentType }: { accessibleUrl: string, contentType: string }) =>
loadModel(world, accessibleUrl, contentType, true),
[MediaType.PDF]: (world: HubsWorld, { accessibleUrl }: { accessibleUrl: string }) =>
loadPDF(world, accessibleUrl),
[MediaType.AUDIO]: (world: HubsWorld, { accessibleUrl }: { accessibleUrl: string }) =>
loadAudio(world, accessibleUrl),
[MediaType.HTML]: (world: HubsWorld, { canonicalUrl, thumbnail }: { canonicalUrl: string; thumbnail: string }) =>
[MediaType.HTML]: (world: HubsWorld, { canonicalUrl, thumbnail }: { canonicalUrl: string, thumbnail: string }) =>
loadHtml(world, canonicalUrl, thumbnail)
};

Expand Down
3 changes: 1 addition & 2 deletions src/bit-systems/network-receive-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { addComponent, defineQuery, enterQuery, hasComponent, removeComponent, r
import { HubsWorld } from "../app";
import { Networked, Owned } from "../bit-components";
import { renderAsNetworkedEntity } from "../utils/create-networked-entity";
import { deleteEntityState, hasSavedEntityState } from "../utils/entity-state-utils";
import { createEntityState, deleteEntityState, hasSavedEntityState } from "../utils/entity-state-utils";
import { networkableComponents, schemas, StoredComponent } from "../utils/network-schemas";
import type { ClientID, CursorBufferUpdateMessage, EntityID, StringID, UpdateMessage } from "../utils/networking-types";
import { hasPermissionToSpawn } from "../utils/permissions";
Expand Down Expand Up @@ -141,7 +141,6 @@ export function networkReceiveSystem(world: HubsWorld) {
world.ignoredNids.add(nid);
continue;
}

renderAsNetworkedEntity(world, prefabName, initialData, nidString, creator);
}
}
Expand Down
9 changes: 8 additions & 1 deletion src/bit-systems/networking.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { defineQuery } from "bitecs";
import { Networked } from "../bit-components";
import type { CreateMessageData, CreatorChange, EntityID, Message, StringID } from "../utils/networking-types";
import type {
CreateMessageData,
CreatorChange,
EntityID,
Message,
NetworkID,
StringID
} from "../utils/networking-types";
export let localClientID: StringID | null = null;
export function setLocalClientID(clientId: StringID) {
connectedClientIds.add(clientId);
Expand Down
9 changes: 5 additions & 4 deletions src/bit-systems/object-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import {
} from "../bit-components";
import { anyEntityWith, findAncestorWithComponent } from "../utils/bit-utils";
import { createNetworkedEntity } from "../utils/create-networked-entity";
import { createEntityState, deleteEntityState } from "../utils/entity-state-utils";
import HubChannel from "../utils/hub-channel";
import type { EntityID } from "../utils/networking-types";
import { setMatrixWorld } from "../utils/three-utils";
import { deleteTheDeletableAncestor } from "./delete-entity-system";
import { createMessageDatas, isPinned } from "./networking";
import { TRANSFORM_MODE } from "../components/transform-object-button";
import { ScalingHandler } from "../components/scale-button";
import { canPin, setPinned } from "../utils/bit-pinning-helper";

// Working variables.
const _vec3_1 = new Vector3();
Expand Down Expand Up @@ -148,9 +148,9 @@ function cloneObject(world: HubsWorld, sourceEid: EntityID) {

function handleClicks(world: HubsWorld, menu: EntityID, hubChannel: HubChannel) {
if (clicked(world, ObjectMenu.pinButtonRef[menu])) {
createEntityState(hubChannel, world, ObjectMenu.targetRef[menu]);
setPinned(hubChannel, world, ObjectMenu.targetRef[menu], true);
} else if (clicked(world, ObjectMenu.unpinButtonRef[menu])) {
deleteEntityState(hubChannel, world, ObjectMenu.targetRef[menu]);
setPinned(hubChannel, world, ObjectMenu.targetRef[menu], false);
} else if (clicked(world, ObjectMenu.cameraFocusButtonRef[menu])) {
console.log("Clicked focus");
} else if (clicked(world, ObjectMenu.cameraTrackButtonRef[menu])) {
Expand Down Expand Up @@ -203,8 +203,9 @@ function updateVisibility(world: HubsWorld, menu: EntityID, frozen: boolean) {
const obj = world.eid2obj.get(menu)!;
obj.visible = visible;

world.eid2obj.get(ObjectMenu.pinButtonRef[menu])!.visible = visible && !isPinned(target);
world.eid2obj.get(ObjectMenu.unpinButtonRef[menu])!.visible = visible && isPinned(target);
world.eid2obj.get(ObjectMenu.pinButtonRef[menu])!.visible =
visible && !isPinned(target) && canPin(APP.hubChannel!, world, target);

[
ObjectMenu.cameraFocusButtonRef[menu],
Expand Down
18 changes: 14 additions & 4 deletions src/components/media-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import {
proxiedUrlFor,
isHubsRoomUrl,
isLocalHubsSceneUrl,
isLocalHubsAvatarUrl
isLocalHubsAvatarUrl,
isHubsDestinationUrl,
isHubsAvatarUrl,
hubsRoomRegex,
localHubsRoomRegex
} from "../utils/media-url-utils";
import { addAnimationComponents } from "../utils/animation";

Expand Down Expand Up @@ -363,10 +367,16 @@ AFRAME.registerComponent("media-loader", {

// We want to resolve and proxy some hubs urls, like rooms and scene links,
// but want to avoid proxying assets in order for this to work in dev environments
const isLocalModelAsset =
isNonCorsProxyDomain(parsedUrl.hostname) && (guessContentType(src) || "").startsWith("model/gltf");
const isLocalAsset =
isNonCorsProxyDomain(parsedUrl.hostname) &&
!(await isHubsDestinationUrl(src)) &&
!(await isHubsAvatarUrl(src)) &&
!src.match(hubsRoomRegex)?.groups.id &&
!src.match(localHubsRoomRegex)?.groups.id;

if (this.data.resolve && !src.startsWith("data:") && !src.startsWith("hubs:") && !isLocalModelAsset) {
console.log("IS LOCAL ASSET?", isLocalAsset, src);

if (this.data.resolve && !src.startsWith("data:") && !src.startsWith("hubs:") && !isLocalAsset) {
const is360 = !!(this.data.mediaOptions.projection && this.data.mediaOptions.projection.startsWith("360"));
const quality = getDefaultResolveQuality(is360);
const result = await resolveUrl(src, quality, version, forceLocalRefresh);
Expand Down
11 changes: 0 additions & 11 deletions src/components/pinnable.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { addComponent, removeComponent } from "bitecs";
import { Pinnable, Pinned } from "../bit-components";

AFRAME.registerComponent("pinnable", {
schema: {
pinned: { default: false }
Expand All @@ -16,18 +13,10 @@ AFRAME.registerComponent("pinnable", {
this.el.addEventListener("owned-pager-page-changed", this._persist);

this.el.addEventListener("owned-video-state-changed", this._persistAndAnimate);

addComponent(APP.world, Pinnable, this.el.object3D.eid);
},

update() {
this._animate();

if (this.data.pinned) {
addComponent(APP.world, Pinned, this.el.object3D.eid);
} else {
removeComponent(APP.world, Pinned, this.el.object3D.eid);
}
},

_persistAndAnimate() {
Expand Down
6 changes: 5 additions & 1 deletion src/inflators/media-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ export type MediaLoaderParams = {
resize: boolean;
recenter: boolean;
animateLoad: boolean;
fileId?: string;
isObjectMenuTarget: boolean;
};

export function inflateMediaLoader(
world: HubsWorld,
eid: number,
{ src, recenter, resize, animateLoad, isObjectMenuTarget }: MediaLoaderParams
{ src, recenter, resize, animateLoad, fileId, isObjectMenuTarget }: MediaLoaderParams
) {
addComponent(world, MediaLoader, eid);
let flags = 0;
Expand All @@ -23,5 +24,8 @@ export function inflateMediaLoader(
if (animateLoad) flags |= MEDIA_LOADER_FLAGS.ANIMATE_LOAD;
if (isObjectMenuTarget) flags |= MEDIA_LOADER_FLAGS.IS_OBJECT_MENU_TARGET;
MediaLoader.flags[eid] = flags;
if (fileId) {
MediaLoader.fileId[eid] = APP.getSid(fileId)!;
}
MediaLoader.src[eid] = APP.getSid(src)!;
}
2 changes: 2 additions & 0 deletions src/load-media-on-paste-or-drop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export async function spawnFromFileList(files: FileList) {
recenter: true,
resize: !qsTruthy("noResize"),
animateLoad: true,
fileId: response.file_id,
isObjectMenuTarget: true
};
})
Expand All @@ -61,6 +62,7 @@ export async function spawnFromFileList(files: FileList) {
recenter: true,
resize: !qsTruthy("noResize"),
animateLoad: true,
fileId: null,
isObjectMenuTarget: true
};
});
Expand Down
15 changes: 6 additions & 9 deletions src/react-components/room/object-hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ import { removeNetworkedObject } from "../../utils/removeNetworkedObject";
import { rotateInPlaceAroundWorldUp, affixToWorldUp } from "../../utils/three-utils";
import { getPromotionTokenForFile } from "../../utils/media-utils";
import { hasComponent } from "bitecs";
import { Pinnable, Pinned, Static } from "../../bit-components";

function getPinnedState(el) {
return !!(hasComponent(APP.world, Pinnable, el.eid) && hasComponent(APP.world, Pinned, el.eid));
}
import { Static } from "../../bit-components";
import { isPinned as getPinnedState } from "../../bit-systems/networking";

export function isMe(object) {
return object.el.id === "avatar-rig";
Expand All @@ -31,7 +28,7 @@ export function getObjectUrl(object) {
}

export function usePinObject(hubChannel, scene, object) {
const [isPinned, setIsPinned] = useState(getPinnedState(object.el));
const [isPinned, setIsPinned] = useState(getPinnedState(object.el.eid));

const pinObject = useCallback(() => {
const el = object.el;
Expand All @@ -57,11 +54,11 @@ export function usePinObject(hubChannel, scene, object) {
const el = object.el;

function onPinStateChanged() {
setIsPinned(getPinnedState(el));
setIsPinned(getPinnedState(el.eid));
}
el.addEventListener("pinned", onPinStateChanged);
el.addEventListener("unpinned", onPinStateChanged);
setIsPinned(getPinnedState(el));
setIsPinned(getPinnedState(el.eid));
return () => {
el.removeEventListener("pinned", onPinStateChanged);
el.removeEventListener("unpinned", onPinStateChanged);
Expand Down Expand Up @@ -125,7 +122,7 @@ export function useRemoveObject(hubChannel, scene, object) {
const canRemoveObject = !!(
scene.is("entered") &&
!isPlayer(object) &&
!getPinnedState(el) &&
!getPinnedState(el.eid) &&
!hasComponent(APP.world, Static, el.eid) &&
hubChannel.can("spawn_and_move_media")
);
Expand Down
4 changes: 2 additions & 2 deletions src/systems/hold-system.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { addComponent, removeComponent, defineQuery, hasComponent } from "bitecs
import {
Held,
Holdable,
Pinned,
HoveredRemoteRight,
HeldRemoteRight,
HoveredRemoteLeft,
Expand All @@ -15,6 +14,7 @@ import {
AEntity
} from "../bit-components";
import { canMove } from "../utils/permissions-utils";
import { isPinned } from "../bit-systems/networking";

const GRAB_REMOTE_RIGHT = paths.actions.cursor.right.grab;
const DROP_REMOTE_RIGHT = paths.actions.cursor.right.drop;
Expand All @@ -35,7 +35,7 @@ function grab(world, userinput, queryHovered, held, grabPath) {
if (
hovered &&
userinput.get(grabPath) &&
(!hasComponent(world, Pinned, hovered) || AFRAME.scenes[0].is("frozen")) &&
(!isPinned(hovered) || AFRAME.scenes[0].is("frozen")) &&
hasPermissionToGrab(world, hovered)
) {
addComponent(world, held, hovered);
Expand Down
15 changes: 3 additions & 12 deletions src/systems/userinput/devices/app-aware-touchscreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,9 @@ import { findRemoteHoverTarget } from "../../../components/cursor-controller";
// import { canMove } from "../../../utils/permissions-utils";
import ResizeObserver from "resize-observer-polyfill";
import { hasComponent } from "bitecs";
import {
AEntity,
HeldRemoteRight,
OffersRemoteConstraint,
Pinnable,
Pinned,
SingleActionButton,
Static
} from "../../../bit-components";
import { AEntity, HeldRemoteRight, OffersRemoteConstraint, SingleActionButton, Static } from "../../../bit-components";
import { anyEntityWith } from "../../../utils/bit-utils";
import { isPinned } from "../../../bit-systems/networking";

const MOVE_CURSOR_JOB = "MOVE CURSOR";
const MOVE_CAMERA_JOB = "MOVE CAMERA";
Expand Down Expand Up @@ -70,14 +63,12 @@ function shouldMoveCursor(touch, rect, raycaster) {
.get(remoteHoverTarget)
.el.matches(".interactable, .interactable *, .occupiable-waypoint-icon, .teleport-waypoint-icon"));

const isPinned =
hasComponent(APP.world, Pinnable, remoteHoverTarget) && hasComponent(APP.world, Pinned, remoteHoverTarget);
const isSceneFrozen = AFRAME.scenes[0].is("frozen");

// TODO isStatic is likely a superfluous check for things matched via OffersRemoteConstraint
const isStatic = hasComponent(APP.world, Static, remoteHoverTarget);
return (
isSingleActionButton || (isInteractable && (isSceneFrozen || !isPinned) && !isStatic)
isSingleActionButton || (isInteractable && (isSceneFrozen || !isPinned(remoteHoverTarget)) && !isStatic)
// TODO check canMove
//&& (remoteHoverTarget && canMove(remoteHoverTarget))
);
Expand Down
49 changes: 49 additions & 0 deletions src/utils/bit-pinning-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { HubsWorld } from "../app";
import { createEntityState, deleteEntityState } from "./entity-state-utils";
import HubChannel from "./hub-channel";
import { EntityID } from "./networking-types";
import { takeOwnership } from "./take-ownership";
import { createMessageDatas, isNetworkInstantiated, isPinned } from "../bit-systems/networking";

export const setPinned = async (hubChannel: HubChannel, world: HubsWorld, eid: EntityID, shouldPin: boolean) => {
_signInAndPinOrUnpinElement(hubChannel, world, eid, shouldPin);
};

const _pinElement = async (hubChannel: HubChannel, world: HubsWorld, eid: EntityID) => {
try {
await createEntityState(hubChannel, world, eid);
takeOwnership(world, eid);
} catch (e) {
if (e.reason === "invalid_token") {
// TODO: Sign out and sign in again
console.log("PinningHelper: Pin failed due to invalid token, signing out and trying again", e);
} else {
console.warn("PinningHelper: Pin failed for unknown reason", e);
}
}
};

const unpinElement = (hubChannel: HubChannel, world: HubsWorld, eid: EntityID) => {
deleteEntityState(hubChannel, world, eid);
};

const _signInAndPinOrUnpinElement = (hubChannel: HubChannel, world: HubsWorld, eid: EntityID, shouldPin: boolean) => {
const action = shouldPin ? () => _pinElement(hubChannel, world, eid) : () => unpinElement(hubChannel, world, eid);
// TODO: Perform conditional sign in
action();
};

export const canPin = (hubChannel: HubChannel, world: HubsWorld, eid: EntityID): boolean => {
const {
initialData: { fileId }
} = createMessageDatas.get(eid)!;
const hasFile = !!fileId;
const hasPromotableFile =
hasFile && APP.store.state.uploadPromotionTokens.some((upload: any) => upload.fileId === fileId);
return (
isNetworkInstantiated(eid) &&
!isPinned(eid) &&
hubChannel.can("pin_objects") && // TODO: Remove once conditional sign in is implemented
(!hasFile || hasPromotableFile)
);
};
Loading

0 comments on commit d52bd00

Please sign in to comment.