Merge pull request #3 from JoeLudwig/joe_local_storage_plus_multi_user
Added multi-user support
This commit is contained in:
commit
83c15dcaf2
2 changed files with 203 additions and 27 deletions
|
@ -5,7 +5,7 @@ import Dropzone from 'react-dropzone';
|
||||||
|
|
||||||
interface ImageAddedProps
|
interface ImageAddedProps
|
||||||
{
|
{
|
||||||
addImageCallback: ( url: string ) => void;
|
addImageCallback: ( url: string, remoteUrl: string ) => void;
|
||||||
validateUrlCallback: ( url: string ) => boolean;
|
validateUrlCallback: ( url: string ) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,8 @@ export class ImageAdder extends React.Component< ImageAddedProps, ImageAdderStat
|
||||||
{
|
{
|
||||||
if (this.props.validateUrlCallback(this.state.url))
|
if (this.props.validateUrlCallback(this.state.url))
|
||||||
{
|
{
|
||||||
this.props.addImageCallback( this.state.url );
|
// use the same URL for both ends for hosted images
|
||||||
|
this.props.addImageCallback( this.state.url, this.state.url );
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -65,11 +66,12 @@ export class ImageAdder extends React.Component< ImageAddedProps, ImageAdderStat
|
||||||
{
|
{
|
||||||
let res = await this.ipfsNode.add( new Uint8Array( result ) );
|
let res = await this.ipfsNode.add( new Uint8Array( result ) );
|
||||||
|
|
||||||
const url = "/ipfs/" + res.cid;
|
// Use a hosted URL for the remote end
|
||||||
|
const url = "https://ipfs.io/ipfs/" + res.cid;
|
||||||
console.log( `Adding ${ file.name } as ${ url }` );
|
console.log( `Adding ${ file.name } as ${ url }` );
|
||||||
const blobData: ArrayBuffer[] = [result];
|
const blobData: ArrayBuffer[] = [result];
|
||||||
const imageBlob = new Blob(blobData);
|
const imageBlob = new Blob(blobData);
|
||||||
this.props.addImageCallback( URL.createObjectURL(imageBlob) );
|
this.props.addImageCallback( URL.createObjectURL(imageBlob), url );
|
||||||
//this.props.addImageCallback("https://ipfs.io/ipfs/" + res.cid) <- this system is more efficient but slower as it relies on waiting for a return from the ipfs server and that server gets alot of requests
|
//this.props.addImageCallback("https://ipfs.io/ipfs/" + res.cid) <- this system is more efficient but slower as it relies on waiting for a return from the ipfs server and that server gets alot of requests
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
218
src/main.tsx
218
src/main.tsx
|
@ -20,20 +20,43 @@ class MenuItem extends React.Component< {displayImage, onClick, deleteSelfCallba
|
||||||
<button className = "imageMenuButton" onClick = {this.props.onClick}>
|
<button className = "imageMenuButton" onClick = {this.props.onClick}>
|
||||||
<img src = {this.props.displayImage} className = "imageMenuImage"/>
|
<img src = {this.props.displayImage} className = "imageMenuImage"/>
|
||||||
</button>
|
</button>
|
||||||
<button className = "imageMenuDeleteButton" onClick = {this.props.deleteSelfCallback}>X</button>
|
{ !AvGadget.instance().isRemote &&
|
||||||
|
<button className = "imageMenuDeleteButton" onClick = {this.props.deleteSelfCallback}>X</button> }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImageMenuState
|
interface ImageMenuEntry
|
||||||
{
|
{
|
||||||
imageUrls: string[];
|
localUrl: string;
|
||||||
|
remoteUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ImageMenu extends React.Component< {}, ImageMenuState> //class for the whole menu, basically just renders MenuItems according to list of images
|
enum ImageMenuEventType
|
||||||
{
|
{
|
||||||
imageToDisplay: string;
|
SetImage = "set_image",
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImageMenuEvent
|
||||||
|
{
|
||||||
|
type: ImageMenuEventType;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImageMenuState
|
||||||
|
{
|
||||||
|
imageUrls: ImageMenuEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImageMenuProps
|
||||||
|
{
|
||||||
|
sendEventCallback: ( event: ImageMenuEvent ) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImageMenu extends React.Component< ImageMenuProps, ImageMenuState> //class for the whole menu, basically just renders MenuItems according to list of images
|
||||||
|
{
|
||||||
|
imageToDisplay: ImageMenuEntry = null;
|
||||||
|
|
||||||
constructor(props)
|
constructor(props)
|
||||||
{
|
{
|
||||||
|
@ -43,31 +66,48 @@ class ImageMenu extends React.Component< {}, ImageMenuState> //class for the who
|
||||||
{
|
{
|
||||||
imageUrls: [],
|
imageUrls: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
this.imageToDisplay = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
public onAddImage( url: string )
|
public onAddImage( localUrl: string, remoteUrl: string )
|
||||||
{
|
{
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
imageUrls: [ ...this.state.imageUrls, url]
|
imageUrls:
|
||||||
|
[
|
||||||
|
...this.state.imageUrls,
|
||||||
|
{
|
||||||
|
localUrl,
|
||||||
|
remoteUrl,
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
public validateUrl(url:string)
|
public validateUrl( localUrl: string )
|
||||||
{
|
{
|
||||||
return(url && !this.state.imageUrls.includes(url) ? true : false)
|
return localUrl && this.findImageIndex( localUrl ) == -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public displayImage(image: string) //given to buttons, by setting the image to display we stop drawing the menu and start drawing the image, remove image undoes this, we also force an update here since we dont use state
|
private findImageIndex( localUrl: string )
|
||||||
|
{
|
||||||
|
return this.state.imageUrls.findIndex( ( value: ImageMenuEntry ) => value.localUrl == localUrl );
|
||||||
|
}
|
||||||
|
|
||||||
|
public displayImage( image: ImageMenuEntry ) //given to buttons, by setting the image to display we stop drawing the menu and start drawing the image, remove image undoes this, we also force an update here since we dont use state
|
||||||
{
|
{
|
||||||
this.imageToDisplay = image;
|
this.imageToDisplay = image;
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
|
|
||||||
|
this.props.sendEventCallback(
|
||||||
|
{
|
||||||
|
type: ImageMenuEventType.SetImage,
|
||||||
|
url: image.remoteUrl,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeImage()
|
public removeImage()
|
||||||
|
@ -76,20 +116,26 @@ class ImageMenu extends React.Component< {}, ImageMenuState> //class for the who
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteListItem(item: string)
|
public deleteListItem(localUrl: string)
|
||||||
{
|
{
|
||||||
this.state.imageUrls.splice(this.state.imageUrls.indexOf(item), 1);
|
let i = this.findImageIndex( localUrl );
|
||||||
|
if( i != -1 )
|
||||||
|
{
|
||||||
|
this.state.imageUrls.splice( i, 1);
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public render()
|
public render()
|
||||||
{
|
{
|
||||||
if (this.imageToDisplay){ //if theres an image then show that, and also a back button
|
if (this.imageToDisplay){ //if theres an image then show that, and also a back button
|
||||||
|
const url = AvGadget.instance().isRemote ? this.imageToDisplay.remoteUrl : this.imageToDisplay.localUrl;
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<div>
|
<div>
|
||||||
<button className = "imageDisplayBackButton" onClick = {() => this.removeImage()}>ᐊ</button>
|
<button className = "imageDisplayBackButton" onClick = {() => this.removeImage()}>ᐊ</button>
|
||||||
<div style = {{textAlign: "center"}}>
|
<div style = {{textAlign: "center"}}>
|
||||||
<img className = "displayedImage" src = {this.imageToDisplay}/>
|
<img className = "displayedImage" src = { url }/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -104,7 +150,7 @@ class ImageMenu extends React.Component< {}, ImageMenuState> //class for the who
|
||||||
};
|
};
|
||||||
return(
|
return(
|
||||||
<div style = {itemStyle}>
|
<div style = {itemStyle}>
|
||||||
<MenuItem displayImage = {image} onClick = {() => this.displayImage(image)} deleteSelfCallback = {() => this.deleteListItem(image)}/>
|
<MenuItem displayImage = {image.localUrl} onClick = {() => this.displayImage(image)} deleteSelfCallback = {() => this.deleteListItem( image.localUrl )}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -149,15 +195,32 @@ const k_popupHtml =
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
interface MyGadgetState
|
||||||
|
{
|
||||||
|
remoteUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
class MyGadget extends React.Component< {}, {} >
|
interface MyGadgetRemoteParams
|
||||||
|
{
|
||||||
|
remoteUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const k_ImageGalleryInterface = "image-gallery@1";
|
||||||
|
|
||||||
|
class MyGadget extends React.Component< {}, MyGadgetState >
|
||||||
{
|
{
|
||||||
private addImagePopup: Window = null;
|
private addImagePopup: Window = null;
|
||||||
private imageMenuRef = React.createRef<ImageMenu>();
|
private imageMenuRef = React.createRef<ImageMenu>();
|
||||||
|
private grabbableRef = React.createRef<AvStandardGrabbable>();
|
||||||
|
|
||||||
constructor( props: any )
|
constructor( props: any )
|
||||||
{
|
{
|
||||||
super( props );
|
super( props );
|
||||||
|
|
||||||
|
this.state =
|
||||||
|
{
|
||||||
|
remoteUrl: null,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public openWindow(){
|
public openWindow(){
|
||||||
|
@ -168,24 +231,135 @@ class MyGadget extends React.Component< {}, {} >
|
||||||
this.addImagePopup.document.getElementById( "root" ) );
|
this.addImagePopup.document.getElementById( "root" ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
public render()
|
private onRemoteEvent( event: ImageMenuEvent )
|
||||||
{
|
{
|
||||||
|
switch( event.type )
|
||||||
|
{
|
||||||
|
case ImageMenuEventType.SetImage:
|
||||||
|
{
|
||||||
|
this.setState( { remoteUrl: event.url } );
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
private sendRemoteEvent( event: ImageMenuEvent )
|
||||||
|
{
|
||||||
|
if( event.type == ImageMenuEventType.SetImage )
|
||||||
|
{
|
||||||
|
this.setState( { remoteUrl: event.url } );
|
||||||
|
}
|
||||||
|
this.grabbableRef.current?.sendRemoteEvent( event, true );
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount()
|
||||||
|
{
|
||||||
|
if( AvGadget.instance().isRemote )
|
||||||
|
{
|
||||||
|
let params = AvGadget.instance().findInitialInterface( k_ImageGalleryInterface )?.params as MyGadgetRemoteParams;
|
||||||
|
if( params?.remoteUrl )
|
||||||
|
{
|
||||||
|
this.setState( { remoteUrl: params.remoteUrl } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderLocal()
|
||||||
|
{
|
||||||
|
let remoteInitLocks: InitialInterfaceLock[] = [];
|
||||||
|
|
||||||
|
if( this.state.remoteUrl )
|
||||||
|
{
|
||||||
|
remoteInitLocks.push( {
|
||||||
|
iface: k_ImageGalleryInterface,
|
||||||
|
receiver: null,
|
||||||
|
params:
|
||||||
|
{
|
||||||
|
remoteUrl: this.state.remoteUrl,
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ "FullPage" } >
|
<div className={ "FullPage" } >
|
||||||
<div>
|
<div>
|
||||||
<AvStandardGrabbable modelUri={ "models/HandleModel.glb" } modelScale={ 0.8 }
|
<AvStandardGrabbable modelUri={ "models/HandleModel.glb" }
|
||||||
style={ GrabbableStyle.Gadget }>
|
modelScale={ 0.8 }
|
||||||
|
style={ GrabbableStyle.Gadget }
|
||||||
|
remoteInterfaceLocks={ remoteInitLocks }
|
||||||
|
ref={ this.grabbableRef }
|
||||||
|
>
|
||||||
<AvTransform translateY={ 0.21 } >
|
<AvTransform translateY={ 0.21 } >
|
||||||
<AvPanel interactive={true} widthInMeters={ 0.3 }/>
|
<AvPanel interactive={true} widthInMeters={ 0.3 }/>
|
||||||
</AvTransform>
|
</AvTransform>
|
||||||
</AvStandardGrabbable>
|
</AvStandardGrabbable>
|
||||||
</div>
|
</div>
|
||||||
<ImageMenu ref={ this.imageMenuRef }/>
|
<ImageMenu ref={ this.imageMenuRef }
|
||||||
|
sendEventCallback={ this.sendRemoteEvent }/>
|
||||||
<button id = "uploadButton" onClick = { () => this.openWindow() }>🗅</button>
|
<button id = "uploadButton" onClick = { () => this.openWindow() }>🗅</button>
|
||||||
</div> );
|
</div> );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderRemoteImage()
|
||||||
|
{
|
||||||
|
if (this.state.remoteUrl)
|
||||||
|
{
|
||||||
|
//if theres an image then show that
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
<div style = {{textAlign: "center"}}>
|
||||||
|
<img className = "displayedImage" src = { this.state.remoteUrl }/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return(
|
||||||
|
<div id = "noImageText">
|
||||||
|
The owner hasn't selected an image.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderRemote()
|
||||||
|
{
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ "FullPage" } >
|
||||||
|
<div>
|
||||||
|
<AvStandardGrabbable modelUri={ "models/HandleModel.glb" }
|
||||||
|
modelScale={ 0.8 }
|
||||||
|
style={ GrabbableStyle.Gadget }
|
||||||
|
remoteInterfaceLocks={ [] }
|
||||||
|
remoteGadgetCallback={ this.onRemoteEvent }
|
||||||
|
ref={ this.grabbableRef }
|
||||||
|
>
|
||||||
|
<AvTransform translateY={ 0.21 } >
|
||||||
|
<AvPanel interactive={true} widthInMeters={ 0.3 }/>
|
||||||
|
</AvTransform>
|
||||||
|
</AvStandardGrabbable>
|
||||||
|
{ this.renderRemoteImage() }
|
||||||
|
</div>
|
||||||
|
</div> );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public render()
|
||||||
|
{
|
||||||
|
if( AvGadget.instance().isRemote )
|
||||||
|
{
|
||||||
|
return this.renderRemote();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this.renderLocal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAardvarkRoot( "root", <MyGadget/> );
|
renderAardvarkRoot( "root", <MyGadget/> );
|
||||||
|
|
Loading…
Add table
Reference in a new issue