mirror of
https://github.com/morten-olsen/bob-the-algorithm.git
synced 2026-02-08 00:46:25 +01:00
fixes
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@morten-olsen/bob",
|
||||
"version": "3.0.0-alpha.1",
|
||||
"version": "3.0.0",
|
||||
"main": "./src/index.ts",
|
||||
"homepage": "/bob-the-algorithm",
|
||||
"scripts": {
|
||||
@@ -37,7 +37,7 @@
|
||||
"expo-font": "~10.0.4",
|
||||
"expo-linking": "~3.0.0",
|
||||
"expo-location": "~14.0.1",
|
||||
"expo-random": "^12.1.2",
|
||||
"expo-random": "~12.1.1",
|
||||
"expo-splash-screen": "~0.14.0",
|
||||
"expo-status-bar": "~1.2.0",
|
||||
"expo-task-manager": "~10.1.0",
|
||||
@@ -50,8 +50,8 @@
|
||||
"react-native-calendar-strip": "^2.2.5",
|
||||
"react-native-calendars": "^1.1284.0",
|
||||
"react-native-collapsible": "^1.6.0",
|
||||
"react-native-gesture-handler": "^2.4.2",
|
||||
"react-native-get-random-values": "^1.8.0",
|
||||
"react-native-gesture-handler": "~2.1.0",
|
||||
"react-native-get-random-values": "~1.7.0",
|
||||
"react-native-safe-area-context": "3.3.2",
|
||||
"react-native-screens": "~3.10.1",
|
||||
"react-native-web": "0.17.1",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useAsync } from "#/features/async";
|
||||
import { ReactNode } from "react"
|
||||
import { ReactNode, useState } from "react"
|
||||
import { Platform } from "react-native";
|
||||
import { AppointmentsContext, AppointmentsContextValue, AppointmentsStatus } from './context';
|
||||
import { NativeIntegtration } from "./providers/native";
|
||||
import { IntegrationProvider } from "./providers/provider";
|
||||
|
||||
type AppointmentsProviderProps = {
|
||||
children: ReactNode;
|
||||
@@ -10,12 +12,22 @@ type AppointmentsProviderProps = {
|
||||
const AppointmentsProvider: React.FC<AppointmentsProviderProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [provider, setProvider] = useState<IntegrationProvider>();
|
||||
const [value] = useAsync<AppointmentsContextValue>(
|
||||
async () => {
|
||||
if (Platform.OS !== 'ios') {
|
||||
return { status: AppointmentsStatus.unavailable };
|
||||
}
|
||||
return { status: AppointmentsStatus.unavailable };
|
||||
const iosProvider = new NativeIntegtration();
|
||||
const setupSuccess = await iosProvider.setup();
|
||||
if (!setupSuccess) {
|
||||
return { status: AppointmentsStatus.unavailable };
|
||||
}
|
||||
setProvider(iosProvider);
|
||||
return {
|
||||
status: AppointmentsStatus.approved,
|
||||
getDay: iosProvider.getDay,
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
55
packages/app/src/features/appointments/providers/native.ts
Normal file
55
packages/app/src/features/appointments/providers/native.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Appointment, TaskType, timeUtils } from "#/features/data";
|
||||
import { Day, dayUtils } from "#/features/day";
|
||||
import { set } from "date-fns";
|
||||
import { EntityTypes, getCalendarsAsync, getEventsAsync, requestCalendarPermissionsAsync } from "expo-calendar";
|
||||
import { Platform } from "react-native";
|
||||
import { IntegrationProvider } from "./provider";
|
||||
|
||||
class NativeIntegtration implements IntegrationProvider {
|
||||
getAllCalendars = async () => {
|
||||
let calendars = await getCalendarsAsync(EntityTypes.EVENT);
|
||||
return calendars;
|
||||
};
|
||||
|
||||
public setup = async () => {
|
||||
if (Platform.OS !== 'ios') {
|
||||
return false;
|
||||
}
|
||||
const { status } = await requestCalendarPermissionsAsync();
|
||||
if (status !== 'granted') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public getDay = async (day: Day) => {
|
||||
const selectedCalendars = await this.getAllCalendars();
|
||||
const start = dayUtils.dayToDate(day)
|
||||
const end = set(start, {
|
||||
hours: 24,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
milliseconds: 0,
|
||||
});
|
||||
const events = await getEventsAsync(selectedCalendars.map(c => c.id), start, end!)
|
||||
return events.filter(a => !a.allDay).map<Appointment>(e => {
|
||||
const startTime = timeUtils.dateToTime(new Date(e.startDate));
|
||||
const endTime = timeUtils.dateToTime(new Date(e.endDate));
|
||||
const duration = timeUtils.timeToMinutes(endTime) - timeUtils.timeToMinutes(startTime);
|
||||
return {
|
||||
id: e.id,
|
||||
type: TaskType.appointment,
|
||||
calendarId: e.calendarId,
|
||||
title: e.title,
|
||||
required: true,
|
||||
startTime: {
|
||||
min: startTime,
|
||||
max: startTime,
|
||||
},
|
||||
duration,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { NativeIntegtration };
|
||||
10
packages/app/src/features/appointments/providers/provider.ts
Normal file
10
packages/app/src/features/appointments/providers/provider.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Appointment } from "#/features/data";
|
||||
import { Day } from "#/features/day";
|
||||
|
||||
interface IntegrationProvider {
|
||||
getAllCalendars: () => Promise<any>;
|
||||
setup: () => Promise<boolean>;
|
||||
getDay: (day: Day) => Promise<Appointment[]>;
|
||||
}
|
||||
|
||||
export { IntegrationProvider };
|
||||
@@ -47,6 +47,13 @@ const timeToDate = (time: Time) => {
|
||||
return new Date(0, 0, 0, time.hour, time.minute);
|
||||
}
|
||||
|
||||
const dateToTime = (date: Date) => {
|
||||
return {
|
||||
hour: date.getHours(),
|
||||
minute: date.getMinutes(),
|
||||
};
|
||||
}
|
||||
|
||||
const add = (a: Time, b: Time | number) => {
|
||||
const toAdd = typeof b === 'number' ? b : b.hour * 60 + b.minute
|
||||
const current = a.hour * 60 + a.minute + toAdd;
|
||||
@@ -60,6 +67,7 @@ const timeUtils = {
|
||||
largerThan,
|
||||
timeToMinutes,
|
||||
timeToDate,
|
||||
dateToTime,
|
||||
max,
|
||||
add,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { buildGraph, Status, Strategies } from "./algorithm/build-graph";
|
||||
import { useContext } from "react";
|
||||
import { useContext, useMemo, useState } from "react";
|
||||
import { add } from 'date-fns';
|
||||
import { PlannerContext } from "./context";
|
||||
import { Task, Time, UserLocation } from "../data";
|
||||
@@ -103,6 +103,7 @@ const usePreparePlan = () => {
|
||||
},
|
||||
duration: firstValue(override?.duration, task.duration),
|
||||
required: firstValue(override?.required, task.required),
|
||||
locations: firstValue(override?.locations, task.locations) || undefined,
|
||||
}
|
||||
return result;
|
||||
}).filter(Boolean).map(a => a as Exclude<typeof a, undefined>);
|
||||
@@ -137,6 +138,7 @@ export const useSetPlanOptions = () => {
|
||||
|
||||
export const usePlan = () => {
|
||||
const [preparePlan] = usePreparePlan();
|
||||
const [result, setResult] = useState<PlanResult | undefined>(undefined);
|
||||
const getTransition = useGetTransition();
|
||||
const options = usePlanOptions();
|
||||
const createPlan = useAsyncCallback(
|
||||
@@ -154,7 +156,9 @@ export const usePlan = () => {
|
||||
}), {} as {[name: string]: PlanResultDay})
|
||||
}
|
||||
const update = (next: PlanResult) => {
|
||||
setResult(next);
|
||||
result = next;
|
||||
return next;
|
||||
}
|
||||
for (let day of prepared.days) {
|
||||
const id = dayUtils.toId(day.day);
|
||||
@@ -211,12 +215,19 @@ export const usePlan = () => {
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
return update({
|
||||
...result,
|
||||
impossible: prepared.goals,
|
||||
};
|
||||
});
|
||||
},
|
||||
[preparePlan, getTransition, options],
|
||||
);
|
||||
return createPlan;
|
||||
const output = useMemo(
|
||||
() => [
|
||||
createPlan[0],
|
||||
{ ...createPlan[1], result },
|
||||
],
|
||||
[createPlan, result],
|
||||
);
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import { dayUtils } from "#/features/day";
|
||||
import { PlanResultDay } from "#/features/planner"
|
||||
import { Body1, Jumbo } from "@morten-olsen/ui";
|
||||
import styled from "styled-components/native";
|
||||
import { PlanDayTask } from "./task";
|
||||
|
||||
type Props = {
|
||||
day: PlanResultDay;
|
||||
};
|
||||
|
||||
const Transit = styled(Body1)`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const PlanDay: React.FC<Props> = ({ day }) => {
|
||||
if (day.status === 'waiting') {
|
||||
return <></>
|
||||
}
|
||||
if (day.status === 'running') {
|
||||
return <Body1>Running</Body1>
|
||||
return <Body1>Running, nodes: {day.nodes}, strategy: {day.strategy}</Body1>
|
||||
}
|
||||
return (
|
||||
<>
|
||||
@@ -20,7 +27,7 @@ const PlanDay: React.FC<Props> = ({ day }) => {
|
||||
if (item.type === 'task') {
|
||||
return <PlanDayTask task={item} />
|
||||
}
|
||||
return <Body1>Transit {item.from.title} to {item.to.title}</Body1>
|
||||
return <Transit>{item.from.title} → {item.to.title}</Transit>
|
||||
})}
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -11,47 +11,6 @@ type Props = {
|
||||
onPress?: () => void;
|
||||
};
|
||||
|
||||
const Time = styled.Text<{background : string}>`
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
`;
|
||||
|
||||
const TimeBox = styled.View<{
|
||||
background: string;
|
||||
}>`
|
||||
margin-right: 10px;
|
||||
width: 50px;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const Filler = styled.View`
|
||||
margin: 10px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const Block = styled.View<{
|
||||
background: string;
|
||||
height: number;
|
||||
}>`
|
||||
background: ${({ background }) => background};
|
||||
height: ${({ height }) => height / 3}px;
|
||||
max-height: 100px;
|
||||
margin: 5px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
border: solid 1px ${({ background }) => background === 'transparent' ? background : chroma(background).darken(0.3).hex()};
|
||||
`;
|
||||
|
||||
const Main = styled.View`
|
||||
flex: 1;
|
||||
`
|
||||
|
||||
const Touch = styled.TouchableOpacity`
|
||||
`;
|
||||
@@ -68,7 +27,6 @@ const PlanDayTask: React.FC<Props> = ({ task, onPress }) => {
|
||||
const view = (
|
||||
<Row>
|
||||
<CalendarEntry
|
||||
location={task.location?.join(', ') || 'anywhere'}
|
||||
start={timeUtils.timeToDate(task.start)}
|
||||
end={timeUtils.timeToDate(task.end)}
|
||||
title={task.name}
|
||||
|
||||
@@ -37,7 +37,7 @@ const DayScreen: React.FC = () => {
|
||||
value={startTimeOverride}
|
||||
onChange={setStartTimeOverride}
|
||||
/>
|
||||
{appointmentStatus === AppointmentsStatus.rejected && (
|
||||
{appointmentStatus === AppointmentsStatus.approved && (
|
||||
<TaskGroup type={TaskType.appointment} />
|
||||
)}
|
||||
<TaskGroup type={TaskType.routine} />
|
||||
|
||||
@@ -49,8 +49,6 @@ const OverrideSetScreen: React.FC = () => {
|
||||
const [duration, setDuration] = useState('');
|
||||
const [hasLocation, setHasLocation] = useState(false);
|
||||
const [selectedLocations, setSelectedLocations] = useState<UserLocation[]>([]);
|
||||
const [hasDays, setHasDays] = useState(false);
|
||||
const [selectedDays, setSelectedDays] = useState<typeof days>([]);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
@@ -88,8 +86,6 @@ const OverrideSetScreen: React.FC = () => {
|
||||
duration,
|
||||
hasLocation,
|
||||
selectedLocations,
|
||||
hasDays,
|
||||
selectedDays,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -101,6 +97,10 @@ const OverrideSetScreen: React.FC = () => {
|
||||
[id, clearOverrides],
|
||||
);
|
||||
|
||||
if (!task) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Popup title={`Overrides for ${task.title}`} onClose={goBack}>
|
||||
<SideBySide>
|
||||
@@ -139,20 +139,6 @@ const OverrideSetScreen: React.FC = () => {
|
||||
disabledText="Anywhere"
|
||||
enabledText="Specific location"
|
||||
/>
|
||||
<OptionalSelector
|
||||
label="Days"
|
||||
enabled={hasDays}
|
||||
items={days}
|
||||
selected={selectedDays}
|
||||
onChange={setSelectedDays}
|
||||
render={day=> ({
|
||||
title: day.name
|
||||
})}
|
||||
getKey={day => day.id.toString()}
|
||||
setEnabled={setHasDays}
|
||||
disabledText="Any day"
|
||||
enabledText="Specific days"
|
||||
/>
|
||||
<Checkbox label="Required" />
|
||||
<TextInput
|
||||
label="Priority"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Body1, Caption, Overline } from "../../../typography";
|
||||
import { Title1, Title2, Body1, Caption, Overline } from "../../../typography";
|
||||
import { Row, Cell, Icon, RowProps } from '../../base';
|
||||
import styled from "styled-components/native"
|
||||
import stringToColor from 'string-to-color';
|
||||
@@ -61,8 +61,8 @@ const CalendarEntry = ({
|
||||
height={height}
|
||||
color={color}
|
||||
>
|
||||
<Overline color="background">{location}</Overline>
|
||||
<Body1 color="background">{title}</Body1>
|
||||
{!!location && <Overline color="background">{location}</Overline>}
|
||||
<Title1 color="background">{title}</Title1>
|
||||
<Caption color="background">{time}</Caption>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
@@ -70,7 +70,6 @@ const CalendarStrip: React.FC<Props> = ({ start, selected, onSelect }) => {
|
||||
}, [firstDayOfWeek]);
|
||||
|
||||
const monthLabel = useMemo(() => {
|
||||
console.log(months);
|
||||
if (months[0] === months[1]) {
|
||||
return new Date(0, months[0], 1).toLocaleString('en-us', { month: 'long' });
|
||||
} else {
|
||||
|
||||
@@ -50,7 +50,7 @@ const light: Theme = {
|
||||
},
|
||||
font: {
|
||||
family: Platform.OS === 'web' ? 'Montserrat' : undefined,
|
||||
baseSize: Platform.OS === 'web' ? 12 : 10,
|
||||
baseSize: Platform.OS === 'web' ? 12 : 11,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user