mirror of
https://github.com/urbit/shrub.git
synced 2024-12-19 16:51:42 +03:00
interface: convert launch store to zustand
This commit is contained in:
parent
b8cd15a788
commit
d17794f93d
@ -1,61 +1,70 @@
|
||||
import _ from 'lodash';
|
||||
import { LaunchUpdate } from '~/types/launch-update';
|
||||
import { LaunchState, LaunchUpdate, WeatherState } from '~/types/launch-update';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { StoreState } from '../../store/type';
|
||||
import useLaunchState from '../state/launch';
|
||||
import { compose } from 'lodash/fp';
|
||||
|
||||
type LaunchState = Pick<StoreState, 'launch' | 'weather' | 'userLocation'>;
|
||||
|
||||
export default class LaunchReducer<S extends LaunchState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
export default class LaunchReducer {
|
||||
reduce(json: Cage) {
|
||||
const data = _.get(json, 'launch-update', false);
|
||||
if (data) {
|
||||
this.initial(data, state);
|
||||
this.changeFirstTime(data, state);
|
||||
this.changeOrder(data, state);
|
||||
this.changeFirstTime(data, state);
|
||||
this.changeIsShown(data, state);
|
||||
useLaunchState.setState(
|
||||
compose([
|
||||
initial,
|
||||
changeFirstTime,
|
||||
changeOrder,
|
||||
changeFirstTime,
|
||||
changeIsShown,
|
||||
].map(reducer => reducer.bind(reducer, data))
|
||||
)(useLaunchState.getState())
|
||||
)
|
||||
}
|
||||
|
||||
const weatherData = _.get(json, 'weather', false);
|
||||
const weatherData: WeatherState = _.get(json, 'weather', false);
|
||||
if (weatherData) {
|
||||
state.weather = weatherData;
|
||||
useLaunchState.setState({ weather: weatherData });
|
||||
}
|
||||
|
||||
const locationData = _.get(json, 'location', false);
|
||||
if (locationData) {
|
||||
state.userLocation = locationData;
|
||||
}
|
||||
}
|
||||
|
||||
initial(json: LaunchUpdate, state: S) {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.launch = data;
|
||||
}
|
||||
}
|
||||
|
||||
changeFirstTime(json: LaunchUpdate, state: S) {
|
||||
const data = _.get(json, 'changeFirstTime', false);
|
||||
if (data) {
|
||||
state.launch.firstTime = data;
|
||||
}
|
||||
}
|
||||
|
||||
changeOrder(json: LaunchUpdate, state: S) {
|
||||
const data = _.get(json, 'changeOrder', false);
|
||||
if (data) {
|
||||
state.launch.tileOrdering = data;
|
||||
}
|
||||
}
|
||||
|
||||
changeIsShown(json: LaunchUpdate, state: S) {
|
||||
const data = _.get(json, 'changeIsShown', false);
|
||||
if (data) {
|
||||
const tile = state.launch.tiles[data.name];
|
||||
console.log(tile);
|
||||
if (tile) {
|
||||
tile.isShown = data.isShown;
|
||||
}
|
||||
useLaunchState.setState({ userLocation: locationData });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const initial = (json: LaunchUpdate, state: LaunchState): LaunchState => {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
Object.keys(data).forEach(key => {
|
||||
state[key] = data[key];
|
||||
});
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
export const changeFirstTime = (json: LaunchUpdate, state: LaunchState): LaunchState => {
|
||||
const data = _.get(json, 'changeFirstTime', false);
|
||||
if (data) {
|
||||
state.firstTime = data;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
export const changeOrder = (json: LaunchUpdate, state: LaunchState): LaunchState => {
|
||||
const data = _.get(json, 'changeOrder', false);
|
||||
if (data) {
|
||||
state.tileOrdering = data;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
export const changeIsShown = (json: LaunchUpdate, state: LaunchState): LaunchState => {
|
||||
const data = _.get(json, 'changeIsShown', false);
|
||||
if (data) {
|
||||
const tile = state.tiles[data.name];
|
||||
if (tile) {
|
||||
tile.isShown = data.isShown;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
41
pkg/interface/src/logic/state/launch.tsx
Normal file
41
pkg/interface/src/logic/state/launch.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import React from "react";
|
||||
import create, { State } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { Tile, WeatherState } from "~/types/launch-update";
|
||||
import { stateSetter } from "../lib/util";
|
||||
|
||||
|
||||
export interface LaunchState extends State {
|
||||
firstTime: boolean;
|
||||
tileOrdering: string[];
|
||||
tiles: {
|
||||
[app: string]: Tile;
|
||||
},
|
||||
weather: WeatherState | null,
|
||||
userLocation: string | null;
|
||||
set: (fn: (state: LaunchState) => void) => void;
|
||||
};
|
||||
|
||||
const useLaunchState = create<LaunchState>(persist((set, get) => ({
|
||||
firstTime: true,
|
||||
tileOrdering: [],
|
||||
tiles: {},
|
||||
weather: null,
|
||||
userLocation: null,
|
||||
set: fn => stateSetter(fn, set)
|
||||
}), {
|
||||
name: 'LandscapeLaunchState'
|
||||
}));
|
||||
|
||||
function withLaunchState<P, S extends keyof LaunchState>(Component: any, stateMemberKeys?: S[]) {
|
||||
return React.forwardRef((props: Omit<P, S>, ref) => {
|
||||
const launchState = stateMemberKeys ? useLaunchState(
|
||||
state => stateMemberKeys.reduce(
|
||||
(object, key) => ({ ...object, [key]: state[key] }), {}
|
||||
)
|
||||
): useLaunchState();
|
||||
return <Component ref={ref} {...launchState} {...props} />
|
||||
});
|
||||
}
|
||||
|
||||
export { useLaunchState as default, withLaunchState };
|
@ -106,7 +106,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
this.localReducer.reduce(data, this.state);
|
||||
this.s3Reducer.reduce(data, this.state);
|
||||
this.groupReducer.reduce(data);
|
||||
this.launchReducer.reduce(data, this.state);
|
||||
this.launchReducer.reduce(data);
|
||||
this.connReducer.reduce(data, this.state);
|
||||
GraphReducer(data);
|
||||
HarkReducer(data);
|
||||
|
@ -165,8 +165,6 @@ class App extends React.Component {
|
||||
<ErrorBoundary>
|
||||
<Omnibox
|
||||
associations={state.associations}
|
||||
apps={state.launch}
|
||||
tiles={state.launch.tiles}
|
||||
api={this.api}
|
||||
show={this.props.omniboxShown}
|
||||
toggle={this.props.toggleOmnibox}
|
||||
|
@ -177,11 +177,7 @@ export default function LaunchApp(props) {
|
||||
</Box>
|
||||
</Tile>
|
||||
<Tiles
|
||||
tiles={props.launch.tiles}
|
||||
tileOrdering={props.launch.tileOrdering}
|
||||
api={props.api}
|
||||
location={props.userLocation}
|
||||
weather={props.weather}
|
||||
/>
|
||||
<ModalButton
|
||||
icon="Plus"
|
||||
|
@ -5,50 +5,50 @@ import CustomTile from './tiles/custom';
|
||||
import ClockTile from './tiles/clock';
|
||||
import WeatherTile from './tiles/weather';
|
||||
|
||||
export default class Tiles extends React.PureComponent {
|
||||
render() {
|
||||
const { props } = this;
|
||||
import useLaunchState from '~/logic/state/launch';
|
||||
|
||||
const tiles = props.tileOrdering.filter((key) => {
|
||||
const tile = props.tiles[key];
|
||||
const Tiles = (props) => {
|
||||
const weather = useLaunchState(state => state.weather);
|
||||
const tileOrdering = useLaunchState(state => state.tileOrdering);
|
||||
const tileState = useLaunchState(state => state.tiles);
|
||||
const tiles = tileOrdering.filter((key) => {
|
||||
const tile = tileState[key];
|
||||
|
||||
return tile.isShown;
|
||||
}).map((key) => {
|
||||
const tile = props.tiles[key];
|
||||
if ('basic' in tile.type) {
|
||||
const basic = tile.type.basic;
|
||||
return tile.isShown;
|
||||
}).map((key) => {
|
||||
const tile = tileState[key];
|
||||
if ('basic' in tile.type) {
|
||||
const basic = tile.type.basic;
|
||||
return (
|
||||
<BasicTile
|
||||
key={key}
|
||||
title={basic.title}
|
||||
iconUrl={basic.iconUrl}
|
||||
linkedUrl={basic.linkedUrl}
|
||||
/>
|
||||
);
|
||||
} else if ('custom' in tile.type) {
|
||||
if (key === 'weather') {
|
||||
return (
|
||||
<BasicTile
|
||||
<WeatherTile
|
||||
key={key}
|
||||
title={basic.title}
|
||||
iconUrl={basic.iconUrl}
|
||||
linkedUrl={basic.linkedUrl}
|
||||
api={props.api}
|
||||
/>
|
||||
);
|
||||
} else if ('custom' in tile.type) {
|
||||
if (key === 'weather') {
|
||||
return (
|
||||
<WeatherTile
|
||||
key={key}
|
||||
api={props.api}
|
||||
weather={props.weather}
|
||||
location={props.location}
|
||||
/>
|
||||
);
|
||||
} else if (key === 'clock') {
|
||||
const location = 'nearest-area' in props.weather ? props.weather['nearest-area'][0] : '';
|
||||
return (
|
||||
<ClockTile key={key} location={location} />
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return <CustomTile key={key} />;
|
||||
} else if (key === 'clock') {
|
||||
const location = weather && 'nearest-area' in weather ? weather['nearest-area'][0] : '';
|
||||
return (
|
||||
<ClockTile key={key} location={location} />
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return <CustomTile key={key} />;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>{tiles}</React.Fragment>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>{tiles}</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Tiles;
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { Box, Icon, Text, BaseAnchor, BaseInput } from '@tlon/indigo-react';
|
||||
import ErrorBoundary from '~/views/components/ErrorBoundary';
|
||||
import { withLaunchState } from '~/logic/state/launch';
|
||||
|
||||
import Tile from './tile';
|
||||
|
||||
@ -34,7 +35,7 @@ const imperialCountries = [
|
||||
'Liberia',
|
||||
];
|
||||
|
||||
export default class WeatherTile extends React.Component {
|
||||
class WeatherTile extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@ -289,3 +290,4 @@ export default class WeatherTile extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default withLaunchState(WeatherTile);
|
@ -20,6 +20,7 @@ import { ImageInput } from '~/views/components/ImageInput';
|
||||
import { MarkdownField } from '~/views/apps/publish/components/MarkdownField';
|
||||
import { resourceFromPath } from '~/logic/lib/group';
|
||||
import GroupSearch from '~/views/components/GroupSearch';
|
||||
import useContactState from '~/logic/state/contacts';
|
||||
|
||||
const formSchema = Yup.object({
|
||||
nickname: Yup.string(),
|
||||
@ -41,7 +42,8 @@ const emptyContact = {
|
||||
};
|
||||
|
||||
export function EditProfile(props: any): ReactElement {
|
||||
const { contact, ship, api, isPublic } = props;
|
||||
const { contact, ship, api } = props;
|
||||
const isPublic = useContactState(state => state.isContactPublic);
|
||||
const history = useHistory();
|
||||
if (contact) {
|
||||
contact.isPublic = isPublic;
|
||||
|
@ -17,17 +17,19 @@ import { EditProfile } from './EditProfile';
|
||||
import { SetStatusBarModal } from '~/views/components/SetStatusBarModal';
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
import { useTutorialModal } from '~/views/components/useTutorialModal';
|
||||
import useContactState from '~/logic/state/contacts';
|
||||
|
||||
export function Profile(props: any): ReactElement {
|
||||
const { hideAvatars } = useLocalState(({ hideAvatars }) => ({
|
||||
hideAvatars
|
||||
}));
|
||||
const history = useHistory();
|
||||
const nackedContacts = useContactState(state => state.nackedContacts);
|
||||
|
||||
if (!props.ship) {
|
||||
return null;
|
||||
}
|
||||
const { contact, nackedContacts, hasLoaded, isPublic, isEdit, ship } = props;
|
||||
const { contact, hasLoaded, isPublic, isEdit, ship } = props;
|
||||
const nacked = nackedContacts.has(ship);
|
||||
|
||||
useEffect(() => {
|
||||
@ -109,7 +111,6 @@ export function Profile(props: any): ReactElement {
|
||||
s3={props.s3}
|
||||
api={props.api}
|
||||
associations={props.associations}
|
||||
isPublic={isPublic}
|
||||
/>
|
||||
) : (
|
||||
<ViewProfile
|
||||
@ -117,7 +118,6 @@ export function Profile(props: any): ReactElement {
|
||||
nacked={nacked}
|
||||
ship={ship}
|
||||
contact={contact}
|
||||
isPublic={isPublic}
|
||||
associations={props.associations}
|
||||
/>
|
||||
) }
|
||||
|
@ -14,12 +14,15 @@ import RichText from '~/views/components/RichText';
|
||||
import { GroupLink } from '~/views/components/GroupLink';
|
||||
import { lengthOrder } from '~/logic/lib/util';
|
||||
import useLocalState from '~/logic/state/local';
|
||||
import useContactState from '~/logic/state/contacts';
|
||||
|
||||
export function ViewProfile(props: any): ReactElement {
|
||||
const { hideNicknames } = useLocalState(({ hideNicknames }) => ({
|
||||
hideNicknames
|
||||
}));
|
||||
const { api, contact, nacked, isPublic, ship, associations, groups } = props;
|
||||
const { api, contact, nacked, ship, associations, groups } = props;
|
||||
|
||||
const isPublic = useContactState(state => state.isContactPublic);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -21,7 +21,6 @@ export default function ProfileScreen(props: any) {
|
||||
render={({ match }) => {
|
||||
const ship = match.params.ship;
|
||||
const isEdit = match.url.includes('edit');
|
||||
const isPublic = props.isContactPublic;
|
||||
const contact = contacts?.[ship];
|
||||
|
||||
return (
|
||||
@ -45,7 +44,6 @@ export default function ProfileScreen(props: any) {
|
||||
api={props.api}
|
||||
s3={props.s3}
|
||||
isEdit={isEdit}
|
||||
isPublic={isPublic}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
@ -20,6 +20,7 @@ import useContactState from '~/logic/state/contacts';
|
||||
import useGroupState from '~/logic/state/groups';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import useInviteState from '~/logic/state/invite';
|
||||
import useLaunchState from '~/logic/state/launch';
|
||||
|
||||
interface OmniboxProps {
|
||||
associations: Associations;
|
||||
@ -44,6 +45,7 @@ export function Omnibox(props: OmniboxProps) {
|
||||
const contactState = useContactState(state => state.contacts);
|
||||
const notifications = useHarkState(state => state.notifications);
|
||||
const invites = useInviteState(state => state.invites);
|
||||
const tiles = useLaunchState(state => state.tiles);
|
||||
|
||||
const contacts = useMemo(() => {
|
||||
const maybeShip = `~${deSig(query)}`;
|
||||
@ -61,11 +63,11 @@ export function Omnibox(props: OmniboxProps) {
|
||||
return makeIndex(
|
||||
contacts,
|
||||
props.associations,
|
||||
props.tiles,
|
||||
tiles,
|
||||
selectedGroup,
|
||||
groups
|
||||
);
|
||||
}, [location.pathname, contacts, props.associations, groups, props.tiles]);
|
||||
}, [location.pathname, contacts, props.associations, groups, tiles]);
|
||||
|
||||
const onOutsideClick = useCallback(() => {
|
||||
props.show && props.toggle();
|
||||
|
Loading…
Reference in New Issue
Block a user