compassion-global-experience / compassion-design-system

Compassion International's Global Design System
https://designwithcompassion.com
MIT License
6 stars 6 forks source link

Offline/Low Bandwidth Capabilities #47

Open kendrick opened 3 years ago

kendrick commented 3 years ago

Behavior

Components and apps built with the design system should respect neighbors’ data usage preferences and should fail gracefully if bandwidth is low or nonexistent.

Connectivity metadata should be exposed globally via a design system object so that its presence can be depended on from within any component or in any area of an app built with the design system.

For instance, if a neighbor has indicated a preference to reduce data usage via NetworkInformation.saveData, experiences built with the design system should take that into account.

Similarly, if a slow connection is detected via NetworkInformation properties like rtt or effectiveType, experiences built with the design system should offer the neighbor an opportunity to accomplish a task now (selecting videos to upload, for instance) while deferring the data-heavy portion (like performing the upload) until the neighbor indicates they’re ready.

Resources

kendrick commented 3 years ago

@kidroca, I may do some concepting of this in a local branch in the next week or two. Is monitoring this in a Context that wraps the app is the right way to approach it? In a vanilla/web-components app, I was thinking of writing to localStorage instead… but there may be a better way. Would love your thoughts on those approaches.

kidroca commented 3 years ago

To be able to use this with a Context - the user of the design system would have to wrap their app with the context. This makes the library harder to use. Right now you can just use the components, even without wrapping the app with e Theme - we fallback to the default If this would be put in Context it should also be optional as users might not wrap with it at all

Can this information be derived on the fly though? Maybe we can capture the logic that computes this in a separate file and use it wherever we need to Example - the Image component

  1. The image component mounts
  2. Checks current bandwidth, if it's OK it just renders
  3. If it's restricted - renders alternative content
    • adds a listener to retry when connection changes

Using localStorage would be OK to keep an initial value, but it won't work to notify components on change - storage change events are transmitted only to other tabs, so you can't use that to notify a change in a key to the current tab. This means you'll need to setup something global that would serve as a pub/sub for these events If that's the case I'll just skip the whole localStorage part and just have a file with the following interface

utils/network.js
let listeners: Set<(e: Event) => void> = new Set();

/* The latest connection state, this might be a custom format we use internally */
let state: NetworkInformation = navigator.connection

export const getState = (): NetworkInformation => state

export const addListener = (cb: (e: Event) => void) => {
  listeners.add(cb)
  return () => listeners.delete(cb);
}

export const waitToBeOnline = (): Promise<unknown> => {
  let unsubscribe: () => void;

  const promise = new Promise((resolve: Function) => {
    unsubscribe = addListener((event) => {
      // Some condition that satisfies the connection requirements
      if (event.data.downlink > 1) {
        resolve();
      }
    });
  });

 promise.finally(() => unsubscribe());

 return promise;
}

// Have one global listener and delegate to the rest
state.addEventListener('change', (e) => {
  state = e.data;
  listeners.forEach(callback => callback(e));
})

This would allow us to import the file wherever needed and hook to network state It should work for React and web-components projects

waitToBeOnline can use window.addEventListener('online', ...)

kendrick commented 3 years ago

If this would be put in Context it should also be optional as users might not wrap with it at all

Absolutely—there may be lots of apps that assume an always-connected environment (for use at Compassion’s headquarters, for instance) that wouldn’t need to be bothered with an additional context.

Can this information be derived on the fly though?

A hook-driven approach might be perfect, especially if its internals could be used outside of React apps. I’ll start there. 😄