From 60afd6c52c44643daaae2066259074f2c0785716 Mon Sep 17 00:00:00 2001 From: Greg Fodor <gfodor@gmail.com> Date: Sat, 21 Apr 2018 19:17:50 -0700 Subject: [PATCH] Clean up schema of local storage, remove ToS check --- src/assets/stylesheets/profile.scss | 25 ---------- src/assets/translations.data.json | 5 -- src/hub.js | 14 +++--- src/react-components/profile-entry-panel.js | 54 +++++++-------------- src/react-components/ui-root.js | 6 +-- src/storage/store.js | 35 ++++++++----- src/utils/hub-channel.js | 5 +- src/utils/identity.js | 4 +- 8 files changed, 55 insertions(+), 93 deletions(-) diff --git a/src/assets/stylesheets/profile.scss b/src/assets/stylesheets/profile.scss index d392c99ab..479207b3e 100644 --- a/src/assets/stylesheets/profile.scss +++ b/src/assets/stylesheets/profile.scss @@ -64,31 +64,6 @@ margin: 0.5em 0; } - &__terms { - margin-bottom: 16px; - - &__checkbox { - @extend %checkbox; - vertical-align: unset; - } - &__checkbox:checked { - @extend %checkbox-checked; - } - - &__text { - display: inline-block; - max-width: 20em; - } - - &__link { - color: white; - } - - &__link:visited { - color: grey; - } - } - &__form-submit { @extend %bottom-button; margin: 0; diff --git a/src/assets/translations.data.json b/src/assets/translations.data.json index 5a1ad32fa..c5274d398 100644 --- a/src/assets/translations.data.json +++ b/src/assets/translations.data.json @@ -15,11 +15,6 @@ "profile.save": "SAVE", "profile.display_name.validation_warning": "Alphanumerics and hyphens. At least 3 characters, no more than 32", "profile.header": "Your display name:", - "profile.terms.prefix": "I confirm that I am over the age of 13 and agree to the", - "profile.terms.privacy": "privacy policy", - "profile.terms.conjunction": "and", - "profile.terms.tou": "terms of use", - "profile.terms.suffix": ".", "profile.avatar-selector.loading": "Loading Avatars...", "audio.title": "Test your audio", "audio.subtitle-desktop": "Confirm HMD speaker output", diff --git a/src/hub.js b/src/hub.js index 3cd164eb0..5a5dfc3d3 100644 --- a/src/hub.js +++ b/src/hub.js @@ -116,11 +116,11 @@ const concurrentLoadDetector = new ConcurrentLoadDetector(); concurrentLoadDetector.start(); // Always layer in any new default profile bits -store.update({ profile: { ...generateDefaultProfile(), ...(store.state.profile || {}) } }); +store.update({ activity: {}, settings: {}, profile: { ...generateDefaultProfile(), ...(store.state.profile || {}) } }); // Regenerate name to encourage users to change it. -if (!store.state.profile.has_changed_name) { - store.update({ profile: { display_name: generateRandomName() } }); +if (!store.state.activity.hasChangedName) { + store.update({ profile: { displayName: generateRandomName() } }); } function mountUI(scene, props = {}) { @@ -128,7 +128,7 @@ function mountUI(scene, props = {}) { const forcedVREntryType = qs.vr_entry_type || null; const enableScreenSharing = qsTruthy("enable_screen_sharing"); const htmlPrefix = document.body.dataset.htmlPrefix || ""; - const showProfileEntry = !store.state.profile.has_changed_name; + const showProfileEntry = !store.state.activity.hasChangedName; ReactDOM.render( <UIRoot @@ -167,10 +167,10 @@ const onReady = async () => { }; const applyProfileFromStore = playerRig => { - const displayName = store.state.profile.display_name; + const displayName = store.state.profile.displayName; playerRig.setAttribute("player-info", { displayName, - avatarSrc: "#" + (store.state.profile.avatar_id || "botdefault") + avatarSrc: "#" + (store.state.profile.avatarId || "botdefault") }); document.querySelector("a-scene").emit("username-changed", { username: displayName }); }; @@ -244,7 +244,7 @@ const onReady = async () => { if (!qsTruthy("offline")) { document.body.addEventListener("connected", () => { hubChannel.sendEntryEvent().then(() => { - store.update({ lastEnteredAt: moment().toJSON() }); + store.update({ activity: { lastEnteredAt: moment().toJSON() } }); }); remountUI({ occupantCount: NAF.connection.adapter.publisher.initialOccupants.length + 1 }); }); diff --git a/src/react-components/profile-entry-panel.js b/src/react-components/profile-entry-panel.js index d38ac55e3..5414b0229 100644 --- a/src/react-components/profile-entry-panel.js +++ b/src/react-components/profile-entry-panel.js @@ -14,26 +14,28 @@ class ProfileEntryPanel extends Component { constructor(props) { super(props); - const { display_name, avatar_id } = this.props.store.state.profile; - this.state = { display_name, avatar_id }; + const { displayName, avatarId } = this.props.store.state.profile; + this.state = { displayName, avatarId }; this.props.store.addEventListener("statechanged", this.storeUpdated); } storeUpdated = () => { - const { avatar_id, display_name } = this.props.store.state.profile; - this.setState({ avatar_id, display_name }); + const { avatarId, displayName } = this.props.store.state.profile; + this.setState({ avatarId, displayName }); }; saveStateAndFinish = e => { e.preventDefault(); - const has_agreed_to_terms = this.props.store.state.profile.has_agreed_to_terms || this.state.has_agreed_to_terms; - if (!has_agreed_to_terms) return; - const { has_changed_name, display_name } = this.props.store.state.profile; - const hasChangedName = has_changed_name || this.state.display_name !== display_name; + + const { displayName } = this.props.store.state.profile; + const { hasChangedName } = this.props.store.state.activity; + + const hasChangedNowOrPreviously = hasChangedName || this.state.displayName !== displayName; this.props.store.update({ + activity: { + hasChangedName: hasChangedNowOrPreviously + }, profile: { - has_agreed_to_terms: true, - has_changed_name: hasChangedName, ...this.state } }); @@ -48,7 +50,7 @@ class ProfileEntryPanel extends Component { if (e.source !== this.avatarSelector.contentWindow) { return; } - this.setState({ avatar_id: e.data.avatarId }); + this.setState({ avatarId: e.data.avatarId }); }; componentDidMount() { @@ -81,41 +83,19 @@ class ProfileEntryPanel extends Component { <input id="profile-entry-display-name" className="profile-entry__form-field-text" - value={this.state.display_name} - onChange={e => this.setState({ display_name: e.target.value })} + value={this.state.displayName} + onChange={e => this.setState({ displayName: e.target.value })} required - pattern={SCHEMA.definitions.profile.properties.display_name.pattern} + pattern={SCHEMA.definitions.profile.properties.displayName.pattern} title={formatMessage({ id: "profile.display_name.validation_warning" })} ref={inp => (this.nameInput = inp)} /> </label> <iframe className="profile-entry__avatar-selector" - src={`/${this.props.htmlPrefix}avatar-selector.html#avatar_id=${this.state.avatar_id}`} + src={`/${this.props.htmlPrefix}avatar-selector.html#avatar_id=${this.state.avatarId}`} ref={ifr => (this.avatarSelector = ifr)} /> - {!this.props.store.state.profile.has_agreed_to_terms && ( - <label className="profile-entry__terms"> - <input - className="profile-entry__terms__checkbox" - type="checkbox" - required - value={this.state.has_agreed_to_terms} - onChange={e => this.setState({ has_agreed_to_terms: e.target.checked })} - /> - <span className="profile-entry__terms__text"> - <FormattedMessage id="profile.terms.prefix" />{" "} - <a className="profile-entry__terms__link" target="_blank" href="/privacy"> - <FormattedMessage id="profile.terms.privacy" /> - </a>{" "} - <FormattedMessage id="profile.terms.conjunction" />{" "} - <a className="profile-entry__terms__link" target="_blank" href="/terms"> - <FormattedMessage id="profile.terms.tou" /> - </a> - <FormattedMessage id="profile.terms.suffix" /> - </span> - </label> - )} <input className="profile-entry__form-submit" type="submit" value={formatMessage({ id: "profile.save" })} /> </div> </form> diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index 16b0b8944..6f2f5cbcb 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -315,7 +315,7 @@ class UIRoot extends Component { setMediaStreamToDefault = async () => { let hasAudio = false; - const { lastUsedMicDeviceId } = this.props.store.state; + const { lastUsedMicDeviceId } = this.props.store.state.settings; // Try to fetch last used mic, if there was one. if (lastUsedMicDeviceId) { @@ -410,7 +410,7 @@ class UIRoot extends Component { const micDeviceId = this.micDeviceIdForMicLabel(this.micLabelForMediaStream(mediaStream)); if (micDeviceId) { - this.props.store.update({ lastUsedMicDeviceId: micDeviceId }); + this.props.store.update({ settings: { lastUsedMicDeviceId: micDeviceId } }); } this.setState({ micLevelAudioContext, micUpdateInterval }); @@ -752,7 +752,7 @@ class UIRoot extends Component { ) : ( <div className="entry-dialog"> <ProfileInfoHeader - name={this.props.store.state.profile.display_name} + name={this.props.store.state.profile.displayName} onClick={() => this.setState({ showProfileEntry: true })} /> {entryPanel} diff --git a/src/storage/store.js b/src/storage/store.js index 4351ebeda..22828ba9e 100644 --- a/src/storage/store.js +++ b/src/storage/store.js @@ -1,8 +1,7 @@ -import uuid from "uuid/v4"; import { Validator } from "jsonschema"; import { merge } from "lodash"; -const LOCAL_STORE_KEY = "___mozilla_duck"; +const LOCAL_STORE_KEY = "___mozilla_hubs"; const STORE_STATE_CACHE_KEY = Symbol(); const validator = new Validator(); import { EventTarget } from "event-target-shim"; @@ -10,17 +9,32 @@ import { EventTarget } from "event-target-shim"; // Durable (via local-storage) schema-enforced state that is meant to be consumed via forward data flow. // (Think flux but with way less incidental complexity, at least for now :)) export const SCHEMA = { - id: "/MozillaDuckStore", + id: "/MozillaHubsStore", definitions: { profile: { type: "object", additionalProperties: false, properties: { - has_agreed_to_terms: { type: "boolean" }, - has_changed_name: { type: "boolean" }, - display_name: { type: "string", pattern: "^[A-Za-z0-9-]{3,32}$" }, - avatar_id: { type: "string" } + displayName: { type: "string", pattern: "^[A-Za-z0-9-]{3,32}$" }, + avatarId: { type: "string" } + } + }, + + activity: { + type: "object", + additionalProperties: false, + properties: { + hasChangedName: { type: "boolean" }, + lastEnteredAt: { type: "string" } + } + }, + + settings: { + type: "object", + additionalProperties: false, + properties: { + lastUsedMicDeviceId: { type: "string" } } } }, @@ -28,10 +42,9 @@ export const SCHEMA = { type: "object", properties: { - id: { type: "string", pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" }, profile: { $ref: "#/definitions/profile" }, - lastUsedMicDeviceId: { type: "string" }, - lastEnteredAt: { type: "string" } + activity: { $ref: "#/definitions/activity" }, + settings: { $ref: "#/definitions/settings" } }, additionalProperties: false @@ -42,7 +55,7 @@ export default class Store extends EventTarget { super(); if (localStorage.getItem(LOCAL_STORE_KEY) === null) { - localStorage.setItem(LOCAL_STORE_KEY, JSON.stringify({ id: uuid() })); + localStorage.setItem(LOCAL_STORE_KEY, JSON.stringify({})); } } diff --git a/src/utils/hub-channel.js b/src/utils/hub-channel.js index 13e51b21b..aad95ec4e 100644 --- a/src/utils/hub-channel.js +++ b/src/utils/hub-channel.js @@ -46,12 +46,13 @@ export default class HubChannel { getEntryTimingFlags = () => { const entryTimingFlags = { isNewDaily: true, isNewMonthly: true, isNewDayWindow: true, isNewMonthWindow: true }; + const storedLastEnteredAt = this.store.state.activity.lastEnteredAt; - if (!this.store.state.lastEnteredAt) { + if (!storedLastEnteredAt) { return entryTimingFlags; } - const lastEntered = moment(this.store.state.lastEnteredAt); + const lastEntered = moment(storedLastEnteredAt); const lastEnteredPst = moment(lastEntered).tz("America/Los_Angeles"); const nowPst = moment().tz("America/Los_Angeles"); const dayWindowAgo = moment().subtract(1, "day"); diff --git a/src/utils/identity.js b/src/utils/identity.js index db78b027e..2aaabfd89 100644 --- a/src/utils/identity.js +++ b/src/utils/identity.js @@ -101,8 +101,6 @@ export const avatarIds = avatars.map(av => av.id); export function generateDefaultProfile() { return { - has_agreed_to_terms: false, - has_changed_name: false, - avatar_id: selectRandom(avatarIds) + avatarId: selectRandom(avatarIds) }; } -- GitLab