Blockstack.js - Многопользовательское наблюдение за статусом, часть I
Некоторое время я слежу за экосистемой Blockstack и даже стал участником github. Недавно команда запустила сайт для участия, который награждает людей, которые могут внести значимый вклад в команду посредством сообщений в блогах, запросов на вытягивание и т. Д. (Https://contribute.blockstack.org/)
Если вы не знаете, что такое Blockstack и чем они занимаются, я рекомендую вам заглянуть на их основной сайт https://blockstack.org/ и прочитать информационный документ. Этот пост не будет углубляться в цели или видение команды, а скорее будет посвящен техническому подходу к использованию библиотеки blockstack.js.
Программа пожертвований дала мне дополнительную мотивацию для действительно глубокого погружения в учебные пособия по blockstack.js. Особенно выделяется многослойная стойка для хранения от Кена Ляо. Я закончил обучение, но чувствовал, что он неполный и мне нужно немного больше, чтобы его можно было использовать в реальном мире.
Прежде чем вы продолжите, я настоятельно рекомендую (или требовать), чтобы вы прочитали руководство здесь https://blockstack.org/tutorials/multi-player-storage…. Иначе в этом не будет никакого смысла!
Итак, вы закончили учебник. Теперь вы можете видеть статус своего профиля и других. Если вы хотите создать новый статус, это можно сделать с помощью входных параметров. Если вы перейдете на страницу другого человека, вы потеряете права на создание. А как насчет удаления статуса?
Я провел небольшой рефакторинг в части страницы, отображающей статус. Я понял, что потребуется немного логики / критериев, чтобы иметь возможность удалить статус.
Критерий №1: вы должны быть действующим пользователем, чтобы удалить статус.
Критерий №2: вам нужно отобразить кнопку удаления
Критерий № 3: отображать пользователю текст подтверждения, если удаление было намеренным.
Я решил создать компонент статуса, как показано ниже в файлеProfile.jsx
// Old {this.state.statuses.map((status) => ( <div className="status" key={status.id}> {status.text} </div> ) )} // New { this.state.statuses.map((status) => ( <Status key={status.id} status={status} handleDelete={this.handleDelete} isLocal={this.isLocal} /> )) }
Я создал новую функцию под названием handleDelete
. Подобно saveNewStatus
, мне нужна была функция, которая могла бы подключаться к библиотеке blockstack.js, чтобы фильтровать и удалять предполагаемый удаленный статус и использовать функцию putFile
для создания нового status.json
.
handleDelete(id) { const statuses = this.state.statuses.filter((status) => status.id !== id) const options = { encrypt: false } putFile(statusFileName, JSON.stringify(statuses), options) .then(() => { this.setState({ statuses }) }) } }
Как видите, функция handleDelete
относительно похожа на saveNewStatus
. Функция ожидает аргумент id
. С помощью этого аргумента мы можем сравнить и отфильтровать идентификатор из this.state.statuses
. Если вам интересно, почему я применил этот подход в отличие от традиционного метода API DELETE, я рекомендую прочитать эту статью https://blockstack.org/tutorials/managing-data-with-gaia
Для справки - полный Profile.jsx
файл
import React, { Component } from 'react'; import { isSignInPending, loadUserData, Person, getFile, putFile, lookupProfile } from 'blockstack'; import Status from './Status.jsx'; const avatarFallbackImage = 'https://s3.amazonaws.com/onename/avatar-placeholder.png'; const statusFileName = 'statuses.json' export default class Profile extends Component { constructor(props) { super(props); this.state = { person: { name() { return 'Anonymous'; }, avatarUrl() { return avatarFallbackImage; }, }, username: "", newStatus: "", statuses: [], statusIndex: 0, isLoading: false }; this.handleDelete = this.handleDelete.bind(this); this.isLocal = this.isLocal.bind(this); } componentDidMount() { this.fetchData() } handleNewStatusChange(event) { this.setState({ newStatus: event.target.value }) } handleNewStatusSubmit(event) { this.saveNewStatus(this.state.newStatus) this.setState({ newStatus: "" }) } handleDelete(id) { const statuses = this.state.statuses.filter((status) => status.id !== id) const options = { encrypt: false } putFile(statusFileName, JSON.stringify(statuses), options) .then(() => { this.setState({ statuses }) }) } saveNewStatus(statusText) { let statuses = this.state.statuses let status = { id: this.state.statusIndex++, text: statusText.trim(), created_at: Date.now() } statuses.unshift(status) const options = { encrypt: false } putFile(statusFileName, JSON.stringify(statuses), options) .then(() => { this.setState({ statuses: statuses }) }) } fetchData() { if (this.isLocal()) { this.setState({ isLoading: true }) const options = { decrypt: false, zoneFileLookupURL: 'https://core.blockstack.org/v1/names/' } getFile(statusFileName, options) .then((file) => { var statuses = JSON.parse(file || '[]') this.setState({ person: new Person(loadUserData().profile), username: loadUserData().username, statusIndex: statuses.length, statuses: statuses, }) }) .finally(() => { this.setState({ isLoading: false }) }) } else { const username = this.props.match.params.username this.setState({ isLoading: true }) lookupProfile(username) .then((profile) => { this.setState({ person: new Person(profile), username: username }) }) .catch((error) => { console.log('could not resolve profile') }) const options = { username: username, decrypt: false, zoneFileLookupURL: 'https://core.blockstack.org/v1/names/'} getFile(statusFileName, options) .then((file) => { var statuses = JSON.parse(file || '[]') this.setState({ statusIndex: statuses.length, statuses: statuses }) }) .catch((error) => { console.log('could not fetch statuses') }) .finally(() => { this.setState({ isLoading: false }) }) } } isLocal() { return this.props.match.params.username ? false : true } render() { const { handleSignOut } = this.props; const { person } = this.state; const { username } = this.state; return ( !isSignInPending() && person ? <div className="container"> <div className="row"> <div className="col-md-offset-3 col-md-6"> <div className="col-md-12"> <div className="avatar-section"> <img src={ person.avatarUrl() ? person.avatarUrl() : avatarFallbackImage } className="img-rounded avatar" id="avatar-image" /> <div className="username"> <h1> <span id="heading-name">{ person.name() ? person.name() : 'Nameless Person' }</span> </h1> <span>{username}</span> {this.isLocal() && <span> | <a onClick={ handleSignOut.bind(this) }>(Logout)</a> </span> } </div> </div> </div> {this.isLocal() && <div className="new-status"> <div className="col-md-12"> <textarea className="input-status" value={this.state.newStatus} onChange={e => this.handleNewStatusChange(e)} placeholder="What's on your mind?" /> </div> <div className="col-md-12 text-right"> <button className="btn btn-primary btn-lg" onClick={e => this.handleNewStatusSubmit(e)} > Submit </button> </div> </div> } <div className="col-md-12 statuses"> {this.state.isLoading && <span>Loading...</span>} { this.state.statuses.map((status) => ( <Status key={status.id} status={status} handleDelete={this.handleDelete} isLocal={this.isLocal} /> )) } </div> </div> </div> </div> : null ); } }
Для того, чтобы эта функция использовалась в полной мере, нам необходимо, чтобы наш Status
компонент работал правильно.
Для простоты я решил показать исходный код ниже с некоторыми пунктами.
import React, { Component } from 'react'; class Status extends Component { constructor(props) { super(props); this.state = { showDeleteConfirmation: false } this.onDeleteClick = this.onDeleteClick.bind(this); this.toggleDeleteConfirmation = this.toggleDeleteConfirmation.bind(this); } onDeleteClick() { const { status } = this.props; this.props.handleDelete(status.id); } toggleDeleteConfirmation() { this.setState({ showDeleteConfirmation: !this.state.showDeleteConfirmation }) } render() { const { status, isLocal } = this.props; if(!isLocal()) { return ( <div className="status"> <div className="status-text"> {status.text} </div> </div> ) } return ( <div className="status"> <div className="status-text"> {status.text} </div> <div className="status-button-options"> <button className="btn btn-danger status-delete" onClick={this.toggleDeleteConfirmation} disabled={this.state.showDeleteConfirmation} > Delete </button> </div> { this.state.showDeleteConfirmation && <div className="status-delete-confirmation"> <h4>ARE YOU SURE?</h4> <button className="btn btn-danger status-delete-yes" onClick={this.onDeleteClick}> YES </button> <button className="btn btn-info status-delete-no" onClick={this.toggleDeleteConfirmation}> NO </button> </div> } </div> ) } } export default Status;
- свойства статуса передаются через родителя через
Profile.jsx
isLocal
функция передается через родителя черезProfile.jsx
. ЕслиisLocal()
равноfalse
, отображать текст состояния БЕЗ возможности удаления. (Это означает, что вы просматриваете чужой профиль)showDeleteConfirmation
- это внутреннее состояние компонентаStatus
, определяющее, показывать ли текст подтверждения или нет. Если это правда, показать текст подтверждения.- У кнопки удаления есть
onClick
обработчик, который вызываетthis.toggleDeleteConfirmation
. Эта функция отвечает за отображение текста подтверждения при изменении состояния. - Текст подтверждения будет отображаться, как показано ниже.
3а. Кнопка "Удалить" будет отключена через this.state.showDeleteConfirmation
3b. Щелчок NO вызовет this.toggleDeleteConfirmation
и сбросит состояние. Таким образом, скрывая кнопки подтверждения.
3b. При нажатии ДА вызовется this.onDeleteClick
. Внутри этой функции он вызывает родительскую функцию профиля handleDelete
через props this.props.handleDelete(status.id)
, который также включает идентификатор статуса. Таким образом, статус будет удален, и statuses.json
отобразит новые результаты.
4. При просмотре пользователя вам не будет показана опция «Удалить».
Пример: http: // localhost: 8080 / kkomaz.id
Незначительные надстройки CSS
Я добавил небольшое поле между кнопками, чтобы сделать интерфейс более чистым, который вы можете скопировать и вставить в конец файла style.css
.
.status-delete{margin-top: 10px} .status-delete-confirmation {margin-top: 10px} .status-delete-yes{margin-right: 5px}
Заключение
На мой взгляд, это стандартный пост в блоге CRUD, когда новички начинают изучать веб-разработку. Однако этот пост настроен для приложения blockstack.
До внесения изменений у нас была возможность создавать статус и просматривать статусы других профилей. Теперь у нас есть возможность УДАЛИТЬ. Дальше должна быть возможность ИЗМЕНИТЬ статус. Будьте на связи!
Ссылка на Github: