mirror of
https://github.com/morten-olsen/bob-the-algorithm.git
synced 2026-02-08 00:46:25 +01:00
web version
This commit is contained in:
25
.github/workflows/publish-web.yml
vendored
Normal file
25
.github/workflows/publish-web.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Build and Deploy web
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v2.3.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install and Build 🔧
|
||||
run: |
|
||||
yarn install
|
||||
NODE_ENV=production yarn expo build:web
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@4.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: gh-pages
|
||||
folder: web-build
|
||||
@@ -2,6 +2,7 @@
|
||||
"name": "bob",
|
||||
"version": "1.0.0",
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"homepage": "/bob-the-algorithm",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
@@ -50,6 +51,7 @@
|
||||
"@babel/core": "^7.12.9",
|
||||
"@types/chroma-js": "^2.1.3",
|
||||
"@types/react": "~17.0.21",
|
||||
"@types/react-dom": "^18.0.3",
|
||||
"@types/react-native": "~0.64.12",
|
||||
"@types/styled-components-react-native": "^5.1.3",
|
||||
"babel-plugin-module-resolver": "^4.1.0",
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
import { Calendar } from "expo-calendar";
|
||||
import { createContext } from "react";
|
||||
|
||||
type CalendarContextValue = {
|
||||
type RejectedCalendarContextValue = {
|
||||
status: 'rejected';
|
||||
date: Date;
|
||||
setDate: (date: Date) => void;
|
||||
}
|
||||
|
||||
type UnavailableCalendarContextValue = {
|
||||
status: 'unavailable';
|
||||
date: Date;
|
||||
setDate: (date: Date) => void;
|
||||
}
|
||||
|
||||
type AcceptedCalendarContextValue = {
|
||||
status: 'ready';
|
||||
date: Date;
|
||||
setDate: (date: Date) => void;
|
||||
calendars: Calendar[];
|
||||
@@ -11,6 +24,10 @@ type CalendarContextValue = {
|
||||
error?: any;
|
||||
}
|
||||
|
||||
type CalendarContextValue = RejectedCalendarContextValue
|
||||
| UnavailableCalendarContextValue
|
||||
| AcceptedCalendarContextValue
|
||||
|
||||
const CalendarContext = createContext<CalendarContextValue>(undefined as any);
|
||||
|
||||
export type { CalendarContextValue };
|
||||
|
||||
@@ -5,24 +5,39 @@ import { useAsync, useAsyncCallback } from "#/hooks/async";
|
||||
import { createEventAsync, deleteEventAsync, getEventsAsync } from "expo-calendar";
|
||||
import { PlanItem } from "#/types/plans";
|
||||
|
||||
const emptyArray: never[] = [];
|
||||
const emptyFn = () => undefined;
|
||||
|
||||
export const useCalendar = () => {
|
||||
const { calendar } = useContext(CalendarContext);
|
||||
return calendar;
|
||||
const context = useContext(CalendarContext);
|
||||
if (context.status !== 'ready') {
|
||||
return undefined;
|
||||
}
|
||||
return context.calendar;
|
||||
}
|
||||
|
||||
export const useCalendars = () => {
|
||||
const { calendars } = useContext(CalendarContext);
|
||||
return calendars;
|
||||
const context = useContext(CalendarContext);
|
||||
if (context.status !== 'ready') {
|
||||
return emptyArray;
|
||||
}
|
||||
return context.calendars;
|
||||
}
|
||||
|
||||
export const useSelectedCalendars = () => {
|
||||
const { selected } = useContext(CalendarContext);
|
||||
return selected;
|
||||
const context = useContext(CalendarContext);
|
||||
if (context.status !== 'ready') {
|
||||
return emptyArray;
|
||||
}
|
||||
return context.selected;
|
||||
}
|
||||
|
||||
export const useSetSelectedCalendars = () => {
|
||||
const { setSelected } = useContext(CalendarContext);
|
||||
return setSelected;
|
||||
const context = useContext(CalendarContext);
|
||||
if (context.status !== 'ready') {
|
||||
return emptyFn;
|
||||
}
|
||||
return context.setSelected;
|
||||
}
|
||||
|
||||
export const useDate = () => {
|
||||
@@ -40,6 +55,9 @@ export const useCommit = () => {
|
||||
const calendar = useCalendar();
|
||||
const result = useAsyncCallback(
|
||||
async (plan: PlanItem[]) => {
|
||||
if (!calendar) {
|
||||
return;
|
||||
}
|
||||
const end = set(date, {
|
||||
hours: 24,
|
||||
minutes: 0,
|
||||
@@ -64,7 +82,7 @@ export const useCommit = () => {
|
||||
})
|
||||
}
|
||||
},
|
||||
[date],
|
||||
[date, calendar],
|
||||
);
|
||||
|
||||
return result;
|
||||
|
||||
@@ -3,6 +3,7 @@ import React, { ReactNode, useCallback, useMemo, useState } from "react";
|
||||
import { useAsync } from "#/hooks/async";
|
||||
import { CalendarContext } from "./context";
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { Platform } from "react-native";
|
||||
|
||||
const SELECTED_STORAGE_KEY = 'selected_calendars';
|
||||
|
||||
@@ -15,6 +16,8 @@ type CalendarProviderProps = {
|
||||
|
||||
type SetupResponse = {
|
||||
status: 'rejected';
|
||||
} | {
|
||||
status: 'unavailable';
|
||||
} | {
|
||||
status: 'ready';
|
||||
calendar: Calendar;
|
||||
@@ -31,6 +34,9 @@ const CalendarProvider: React.FC<CalendarProviderProps> = ({
|
||||
const [value] = useAsync<SetupResponse>(
|
||||
async () => {
|
||||
const { status } = await requestCalendarPermissionsAsync();
|
||||
if (Platform.OS !== 'ios') {
|
||||
return { status: 'unavailable' };
|
||||
}
|
||||
if (status !== 'granted') {
|
||||
return { status: 'rejected' };
|
||||
}
|
||||
@@ -81,13 +87,22 @@ const CalendarProvider: React.FC<CalendarProviderProps> = ({
|
||||
[value, selectedIds],
|
||||
);
|
||||
|
||||
if (!value || value.status !== 'ready') {
|
||||
if (!value) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
if (value.status !== 'ready') {
|
||||
return (
|
||||
<CalendarContext.Provider value={{ status: value.status, date, setDate }}>
|
||||
{children}
|
||||
</CalendarContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CalendarContext.Provider
|
||||
value={{
|
||||
status: 'ready',
|
||||
setDate,
|
||||
date,
|
||||
selected,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { UserLocation } from "#/types/location";
|
||||
import { createContext } from "react"
|
||||
|
||||
type Routine = {
|
||||
export type Routine = {
|
||||
id: string;
|
||||
title: string;
|
||||
required: boolean;
|
||||
@@ -15,7 +15,7 @@ type Routine = {
|
||||
days?: boolean[];
|
||||
}
|
||||
|
||||
type RoutinesContextValue = {
|
||||
export type RoutinesContextValue = {
|
||||
routines: Routine[];
|
||||
remove: (id: string) => any;
|
||||
set: (routine: Routine) => any;
|
||||
@@ -23,5 +23,4 @@ type RoutinesContextValue = {
|
||||
|
||||
const RoutinesContext = createContext<RoutinesContextValue>(undefined as any);
|
||||
|
||||
export type { Routine, RoutinesContextValue };
|
||||
export { RoutinesContext };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Modal as Wrapper } from 'react-native';
|
||||
import Wrapper from './react-modal';
|
||||
import { Popup } from '../popup';
|
||||
type ModalProps = {
|
||||
visible: boolean;
|
||||
|
||||
43
src/ui/components/modal/react-modal.native.tsx
Normal file
43
src/ui/components/modal/react-modal.native.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import ReactDOM from 'react-dom';
|
||||
import React, { useMemo, useEffect, ReactNode } from 'react';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const Modal: React.FC<Props> = ({ visible, children }) => {
|
||||
const elm = useMemo(() => {
|
||||
const newElm = document.createElement('div');
|
||||
newElm.style.position = 'fixed';
|
||||
newElm.style.display = 'flex';
|
||||
newElm.style.flexDirection = 'column';
|
||||
newElm.style.left = '0px';
|
||||
newElm.style.top = '0px';
|
||||
newElm.style.width = '100%';
|
||||
newElm.style.height = '100%';
|
||||
newElm.style.transition = 'transform 0.3s';
|
||||
newElm.style.transform = 'translateY(100%)';
|
||||
return newElm;
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
document.body.appendChild(elm);
|
||||
return () => {
|
||||
document.body.removeChild(elm);
|
||||
};
|
||||
}, [elm]);
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
elm.style.transform = 'translateY(0)';
|
||||
} else {
|
||||
elm.style.transform = 'translateY(100%)';
|
||||
}
|
||||
}, [elm, visible]);
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
<>{children}</>,
|
||||
elm,
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
3
src/ui/components/modal/react-modal.tsx
Normal file
3
src/ui/components/modal/react-modal.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Modal } from 'react-native';
|
||||
|
||||
export default Modal;
|
||||
@@ -2269,6 +2269,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df"
|
||||
integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==
|
||||
|
||||
"@types/react-dom@^18.0.3":
|
||||
version "18.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.3.tgz#a022ea08c75a476fe5e96b675c3e673363853831"
|
||||
integrity sha512-1RRW9kst+67gveJRYPxGmVy8eVJ05O43hg77G2j5m76/RFJtMbcfAs2viQ2UNsvvDg8F7OfQZx8qQcl6ymygaQ==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-native@^0.65":
|
||||
version "0.65.22"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.65.22.tgz#58eaf2f64eb37fcb5fa9725547a8e043c4ecbc07"
|
||||
|
||||
Reference in New Issue
Block a user