Translations and more documentation

This commit is contained in:
Richard Ramos 2018-07-13 09:18:30 -04:00
parent 68b22a8621
commit 96fb57eae4
7 changed files with 123 additions and 113 deletions

View File

@ -23,17 +23,16 @@ class Ship extends Component {
}
componentDidMount(){
// TODO: cuando se carga el componente se deben buscar los atributos del token
// ============== BEGIN: Function implementation here ================ //
this._loadAttributes();
// ============== END: Function implementation here ================ //
}
_loadAttributes(){
// TODO: implementar carga de atributos aca
// El unico atributo interesante es la imagen
// Guardar el url en this.state.image
// Ejemplo
// ============== BEGIN: Function implementation here ================ //
// TODO: the only attribute we are interested to load is the image
this.setState({image: "./images/sampleShip.png"});
// ============== END: Function implementation here ================ //
}
showSellForm = (show) => {
@ -52,13 +51,13 @@ class Ship extends Component {
}
buyFromStore = () => {
// TODO: comprar token recien creado
// En props esta el atributo 'id' y 'price', que podemos usar para determinar el id del token
// ============== BEGIN: Function implementation here ================ //
// In props you can find the attributes 'id' and 'price' required for buying the token
this.setState({isSubmitting: true});
// Llamar la siguiente funcion para refrescar las listas
this.props.onAction();
this.props.onAction(); // This function needs to be called to reload the lists of spaceships
this.setState({isSubmitting: false});
// ============== END: Function implementation here ================ //
}
buyFromMarket = () => {
@ -86,14 +85,14 @@ class Ship extends Component {
<img src={image} />
<br />
<ul>
<li title="Energia"><i className="fa fa-dashboard" aria-hidden="true"></i> {energy}</li>
<li title="Energy"><i className="fa fa-dashboard" aria-hidden="true"></i> {energy}</li>
<li title="Lasers"><i className="fa fa-crosshairs" aria-hidden="true"></i> {lasers}</li>
<li title="Escudo"><i className="fa fa-shield" aria-hidden="true"></i> {shield}</li>
<li title="Shield"><i className="fa fa-shield" aria-hidden="true"></i> {shield}</li>
</ul>
{ !wallet || marketplace
? <Button disabled={isSubmitting} bsStyle="success" onClick={marketplace ? this.buyFromMarket : this.buyFromStore}>Comprar</Button>
? <Button disabled={isSubmitting} bsStyle="success" onClick={marketplace ? this.buyFromMarket : this.buyFromStore}>Buy</Button>
: (!showSellForm && salesEnabled
? <Button bsStyle="success" className="hiddenOnLeave" onClick={e => { this.showSellForm(true) }}>Vender</Button>
? <Button bsStyle="success" className="hiddenOnLeave" onClick={e => { this.showSellForm(true) }}>Sell</Button>
: '')
}
@ -106,8 +105,8 @@ class Ship extends Component {
onChange={(e) => this.handleChange('sellPrice', e.target.value)} />
<InputGroup.Addon>Ξ</InputGroup.Addon>
</InputGroup>
<Button disabled={isSubmitting} bsStyle="success" onClick={this.sellShip}>Vender</Button>
<Button disabled={isSubmitting} onClick={e => { this.showSellForm(false) }}>Cancelar</Button>
<Button disabled={isSubmitting} bsStyle="success" onClick={this.sellShip}>Sell</Button>
<Button disabled={isSubmitting} onClick={e => { this.showSellForm(false) }}>Cancel</Button>
</Fragment> : ''
}

View File

@ -42,7 +42,7 @@ class ShipList extends Component {
{ wallet ? <EnableSales isSubmitting={this.state.isSubmitting} handleChange={this.enableMarketplace} salesEnabled={this.state.salesEnabled} /> : ''}
{ list.map((ship, i) => <Ship onAction={onAction} wallet={wallet} salesEnabled={salesEnabled} key={i} marketplace={marketplace} {...ship} />) }
{ list.length == 0
? <p>No hay naves disponibles</p>
? <p>No ships available</p>
: ''
}
</div>;

View File

@ -16,35 +16,38 @@ class WithdrawBalance extends Component {
}
componentDidMount(){
// TODO: Al cargar el componente, debemos obtener el balance
// podemos hacerlo llamando a this._getBalance();
// ============== BEGIN: Function implementation here ================ //
this._getBalance();
// ============== END: Function implementation here ================ //
}
_getBalance(){
// TODO: implementar, el estado a actualizar es 'balance'
// ============== BEGIN: Function implementation here ================ //
// TODO: Update this.state.balance
// ============== END: Function implementation here ================ //
}
handleClick(e){
// ============== BEGIN: Function implementation here ================ //
e.preventDefault();
// TODO: este metodo se debe llamar al hacer click en retirar fondos
// Debe extraer el balance total del contrato del token, y actualizar el UI
// para mostrar que no hay balance disponible
// TODO: this method needs to extract the balance from the contract
// and update the UI to show the new balance
this.setState({isSubmitting: true});
this._getBalance();
this.setState({isSubmitting: false});
// ============== END: Function implementation here ================ //
}
render(){
const { balance } = this.state;
return <Grid>
<h4>Fondos</h4>
<Row>
<Col sm={3} md={3}>
Balance Disponible: <b>{ balance } Ξ</b>
<Button onClick={(e) => this.handleClick(e)} disabled={balance == "0"}>Retirar</Button>
Balance: <b>{ balance } Ξ</b>
<Button onClick={(e) => this.handleClick(e)} disabled={balance == "0"}>Withdraw</Button>
</Col>
</Row>
</Grid>;

View File

@ -44,18 +44,19 @@ class App extends Component {
}
_loadMyShips = async () => {
// TODO: aqui nos interesa cargar la lista de naves que posee el usuario
// se espera un array de objetos en el estado myShips
// cada objeto debe tener los siguientes atributos:
// ============== BEGIN: Function implementation here ================ //
// TODO: We want to load the lists of ships a user has
// this.state.myShips expects an array of objects with these attributes:
// {
// id: "id del token",
// energy: "Atributo del token",
// lasers: "Atributo del token",
// shield: "Atributo del token",
// metadataHash: "Atributo del token",
// id: "token Id",
// energy: "token attribute",
// lasers: "token attribute",
// shield: "token attribute",
// metadataHash: "token attribute",
// }
// Ejemplo:
// Example:
const myShip = {
id: 1,
energy: 10,
@ -64,24 +65,27 @@ class App extends Component {
metadataHash: "METADATA"
};
const list = [ myShip ];
this.setState({myShips: list.reverse()});
this.setState({myShips: list});
// ============== END: Function implementation here ================ //
}
_loadShipsForSale = async () => {
// TODO: aqui nos interesa cargar la lista de naves a la venta cuando generamos el token
// se espera un array de objetos en el estado shipsForSale
// cada objeto debe tener los siguientes atributos:
// ============== BEGIN: Function implementation here ================ //
// TODO: load the list of ships for sale
// this.state.shipsForSale expects an array of objects with these attributes:
// {
// price: "precio de venta",
// id: "id del token",
// energy: "Atributo del token",
// lasers: "Atributo del token",
// shield: "Atributo del token",
// metadataHash: "Atributo del token",
// id: "token Id",
// energy: "token attribute",
// lasers: "token attribute",
// shield: "token attribute",
// metadataHash: "token attribute",
// price: "token price"
// }
let list = [];
this.setState({shipsForSale: list.reverse()});
this.setState({shipsForSale: list});
}
_loadMarketPlace = async () => {

View File

@ -4,11 +4,10 @@ import "zeppelin-solidity/contracts/token/ERC721/ERC721Token.sol";
import "zeppelin-solidity/contracts/ownership/Ownable.sol";
/// @title Contrato base para Mexico Workshop
/// @dev En Status tambien hablamos espanol ;)
/// @title SpaceshipToken
contract SpaceshipToken is ERC721Token("CryptoSpaceships", "CST"), Ownable {
// Estructura que representa nuestra nave spacial
// Struct with spaceship attributes
struct Spaceship {
bytes metadataHash; // IPFS Hash
uint8 energy;
@ -16,19 +15,19 @@ contract SpaceshipToken is ERC721Token("CryptoSpaceships", "CST"), Ownable {
uint8 shield;
}
// Todas las naves que se han creado.
// All the spaceships minted
Spaceship[] public spaceships;
// Precio de las naves
mapping(uint => uint) public spaceshipPrices;
uint[] public shipsForSale;
mapping(uint => uint) public indexes; // shipId => shipForSale
/// @notice Crear un tocken
/// @param _metadataHash IPFS hash que contiene la metadata del token
/// @param _energy Atributo: Energia
/// @param _lasers Atributo: Lasers
/// @param _price Precio de venta del token
/// @notice Mint a token
/// @param _metadataHash IPFS hash with the token metadata
/// @param _energy Attribute: Energy
/// @param _lasers Attribute: Lasers
/// @param _price Sale price in Wei
function mint(bytes _metadataHash,
uint8 _energy,
uint8 _lasers,
@ -51,64 +50,59 @@ contract SpaceshipToken is ERC721Token("CryptoSpaceships", "CST"), Ownable {
shipsForSale.push(spaceshipId);
indexes[spaceshipId] = shipsForSale.length - 1;
// _mint es una funcion del contrato ERC721Token que genera el NFT
// El contrato sera dueno de las naves que se generen
// _mint is a function part of ERC721Token that generates the NFT
// The contract will own the newly minted tokens
_mint(address(this), spaceshipId);
}
/// @notice Obtener cantidad de naves a la venta
/// @return Cantidad
/// @notice Get number of spaceships for sale
function shipsForSaleN() public view returns(uint) {
return shipsForSale.length;
}
/// @notice Comprar nave
/// @param _spaceshipId Id del token a comprar
/// @notice Buy spaceship
/// @param _spaceshipId TokenId
function buySpaceship(uint _spaceshipId) public payable {
// Solo se pueden comprar las naves cuyo dueno sea el contrato
// You can only buy the spaceships owned by this contract
require(ownerOf(_spaceshipId) == address(this));
// Se debe enviar al menos el precio de la nave
require(msg.value != 0);
// Value sent should be at least the spaceship price
require(msg.value >= spaceshipPrices[_spaceshipId]);
// Approvamos directamente para evitar tener que crear una transaccion extra
// y luego enviamos la nave a quien origino la transaccion
// We approve the transfer directly to avoid creating two trx
// then we send the token to the sender
tokenApprovals[_spaceshipId] = msg.sender;
safeTransferFrom(address(this), msg.sender, _spaceshipId);
// La eliminamos de la lista para venta
// Esto se ve un poco mas complicado de lo necesario,
// Pero es para borrar elementos del arreglo de forma eficiente
// Delete the token from the list of tokens for sale
uint256 replacer = shipsForSale[shipsForSale.length - 1];
uint256 pos = indexes[_spaceshipId];
shipsForSale[pos] = replacer;
indexes[replacer] = pos;
shipsForSale.length--;
// Reembolsamos el sobrante
uint refund = msg.value - spaceshipPrices[_spaceshipId];
if (refund > 0)
msg.sender.transfer(refund);
}
/// @notice Retirar balance por compras hechas
/// @notice Withdraw sales balance
function withdrawBalance() public onlyOwner {
owner.transfer(address(this).balance);
}
/// @notice Obtener metadata
/// @param _spaceshipId Id del token
/// @return Direccion desde donde obtener la metadata
/// @notice Get Metadata URI
/// @param _spaceshipId TokenID
/// @return IPFS URL of the metadata
function tokenURI(uint256 _spaceshipId) public view returns (string) {
Spaceship storage s = spaceships[_spaceshipId];
return strConcat("https://ipfs.io/ipfs/", string(s.metadataHash));
}
/// @notice Concatenar strings
/// @dev La concatenacion por strings por ahora debe hacerse manual o usando librerias
/// @param _a Primer string
/// @param _b Segundo string
/// @return String concatenado
/// @notice Concatenate strings
/// @param _a First string
/// @param _b Second string
/// @return _a+_b
function strConcat(string _a, string _b) private returns (string) {
bytes memory _ba = bytes(_a);
bytes memory _bb = bytes(_b);

View File

@ -91,6 +91,8 @@ _loadShipsForSale = async () => {
}
```
[IMAGE_HERE]
You may have noticed that the ships that you create are displayed in the UI, however, they lack their image, and the prices are not formatted correctly. We need to work on the file `app/js/components/ship.js` for this.
As usual, start by importing `EmbarkJS` and `web3`, as well as our `SpaceshipToken`
@ -101,33 +103,41 @@ import web3 from "Embark/web3";
import SpaceshipToken from 'Embark/contracts/SpaceshipToken';
```
The best place to load the image is when the component mounts. We will use `EmbarkJS.onReady` to invoke the `_loadAttributes` method
```
componentDidMount(){
EmbarkJS.onReady((err) => {
this._loadAttributes();
});
}
```
The image URL is part of the token attributes stored in IPFS. We need to use the method `EmbarkJS.Storage.get` to obtain the content of the JSON object. We can access the IPFS hash via `this.props.metadataHash`. Remember we store this information in the contract as a `bytes` data type so we need to convert it back to string:
```
_loadAttributes(){
const metadataHash = web3.utils.toAscii(this.props.metadataHash);
EmbarkJS.Storage.get(metadataHash)
.then(content => {
console.log(content);
});
}
```
The content returned is a string, so, before we can access the image attribute, we need to convert this string back to a JSON object using `JSON.parse(string)`. Once you do this, you are able to set the image state, and after reloading the DApp in the browser, the image should load.
TODO:
componentDidMount(){
EmbarkJS.onReady((err) => {
this._loadAttributes();
});
}
_loadAttributes(){
// Cargar los atributos involucra leer la metadata
EmbarkJS.Storage.get(web3.utils.toAscii(this.props.metadataHash))
.then(content => {
const jsonMetadata = JSON.parse(content);
// Podemos usar este metodo
const _url = EmbarkJS.Storage.getUrl(jsonMetadata.imageHash);
// o leer el url que grabamos en la metadata
// const _url = jsonMetadata.image
this.setState({image: _url})
});
}
```
_loadAttributes(){
const metadataHash = web3.utils.toAscii(this.props.metadataHash);
EmbarkJS.Storage.get(metadataHash)
.then(content => {
const jsonMetadata = JSON.parse(content);
this.setState({image: jsonMetadata.image});
});
}
```
Finally, to format the price, on the `render` method, we will update the constant `formattedPrice` to use `web3.utils.fromWei` and convert the value from wei to ether as expected:
@ -348,7 +358,6 @@ componentDidMount(){
}
```
```
_isOwner(){
SpaceshipToken.methods.owner()

View File

@ -4,8 +4,9 @@
## Buying tokens from the marketplace
## Other functionality you could implement
- Cancel sales
- Transfer tokens
- Trade tokens
- Whisper chat
## Other features
There's still some missing functionality on this DApp that would be great to build, and probably make sense to include, such as: Allow the user to cancel sales, to tranfer tokens to other addresses, trading tokens offchain (maybe through Whisper?), etc. This tutorial covers most of the patterns required to implement them, so if you're looking for an extra challenge, you can have fun building these!
## Conclusion
As you can see, interacting with Ethereum and IPFS is easy using Embark, and lets you build truly decentralized applications fast! Are you interested in learning more or contributing to this framework? Visit http://embark.status.im or chat with us on [Gitter](https://embark.status.im/chat/)