public static IObservable <CourseEditorState> Create(CourseEditorActions actions, CourseData courseData, Ref <ImmutableTransform> cameraTransform) { var commands = actions.CreateProp.Select(StoreAction.Create <Unit>("createProp")) .Merge(actions.CreatePropOnLocation.Select(StoreAction.Create <ImmutableTransform>("createPropOnLocation"))) .Merge(actions.DeleteProp.Select(StoreAction.Create <PropId>("deleteProp"))) .Merge(actions.DeleteSelectedProp.Select(StoreAction.Create <Unit>("deleteSelectedProp"))) .Merge(actions.UpdateProp.Select(StoreAction.Create <Tuple <PropId, ImmutableTransform> >("updateProp"))) .Merge(actions.HighlightProp.Select(StoreAction.Create <Maybe <PropId> >("highlightProp"))) .Merge(actions.SelectProp.Select(StoreAction.Create <Maybe <PropId> >("selectProp"))) .Merge(actions.SelectPropType.Select(StoreAction.Create <PropType>("selectPropType"))) .Merge(actions.ReorderProps.Select(StoreAction.Create <IImmutableList <PropId> >("reorderProps"))) .Merge(actions.Undo.Select(StoreAction.Create <Unit>("undo"))) .Merge(actions.Redo.Select(StoreAction.Create <Unit>("redo"))); var initialHistoricalState = courseData.ToCourseEditorState(); var updatesWithHistory = commands .Scan(new AppState <HistoricalCourseEditorState>(initialHistoricalState), (currentAppState, command) => { var currentState = currentAppState.CurrentState; AppState <HistoricalCourseEditorState> newAppState = currentAppState; if (command.Id.Equals("createProp")) { var propTransform = cameraTransform.Deref().TranslateLocally(new Vector3(0, 0, 30)); newAppState = History.AddNewState(currentAppState, currentState.AddProp(propTransform)); } else if (command.Id.Equals("createPropOnLocation")) { newAppState = History.AddNewState(currentAppState, currentState.AddProp((ImmutableTransform)command.Arguments)); } else if (command.Id.Equals("updateProp")) { var args = (Tuple <PropId, ImmutableTransform>)command.Arguments; var prop = currentState.Props[args._1]; var isTransformChanged = !prop.Transform.Equals(args._2); if (isTransformChanged) { var updatedProp = prop.UpdateTransform(args._2); newAppState = History.AddNewState(currentAppState, currentState.UpdateProp(updatedProp)); } else { newAppState = currentAppState; } } else if (command.Id.Equals("deleteProp")) { var propId = (PropId)command.Arguments; if (currentState.PropOrder.Contains(propId)) { newAppState = History.AddNewState(currentAppState, currentState.DeleteProp(propId)); } } else if (command.Id.Equals("deleteSelectedProp")) { if (currentState.SelectedProp.IsJust) { newAppState = History.AddNewState(currentAppState, currentState.DeleteProp(currentState.SelectedProp.Value)); } } else if (command.Id.Equals("selectProp")) { var selectedPropId = (Maybe <PropId>)command.Arguments; if (selectedPropId.IsNothing || (selectedPropId.IsJust && currentState.Props.ContainsKey(selectedPropId.Value))) { newAppState = History.AddNewState(currentAppState, currentState.SelectProp(selectedPropId)); } } else if (command.Id.Equals("selectPropType")) { newAppState = History.AddNewState(currentAppState, currentState.SelectPropType((PropType)command.Arguments)); } else if (command.Id.Equals("reorderProps")) { newAppState = History.AddNewState(currentAppState, currentState.UpdatePropOrder((IImmutableList <PropId>)command.Arguments)); } else if (command.Id.Equals("undo")) { newAppState = History.Undo(currentAppState); } else if (command.Id.Equals("redo")) { newAppState = History.Redo(currentAppState); } else { newAppState = currentAppState; } return(newAppState); }) .Select(appState => { return(appState.CurrentState.UpdateUndoRedoAvailability( !appState.UndoStack.IsEmpty, !appState.RedoStack.IsEmpty)); }) .StartWith(initialHistoricalState) .Publish(); var initialTransientState = new TransientCourseEditorState(courseData.Name, Maybe.Nothing <PropId>(), TransformTool.Move); var transientUpdates = actions.HighlightProp.Select(StoreAction.Create <Maybe <PropId> >("highlightProp")) .Merge(actions.SelectTransformTool.Select(StoreAction.Create <TransformTool>("selectTransformTool"))) .Merge(actions.UpdateName.Select(StoreAction.Create <string>("updateName"))) .Scan(initialTransientState, (state, command) => { if (command.Id.Equals("highlightProp")) { return(state.HighlightProp((Maybe <PropId>)command.Arguments)); } else if (command.Id.Equals("selectTransformTool")) { // TODO Prevent transform tool selection when no prop is selected return(state.SelectTransformTool((TransformTool)command.Arguments)); } else if (command.Id.Equals("updateName")) { return(state.UpdateName((string)command.Arguments)); } return(state); }) .DistinctUntilChanged() .StartWith(initialTransientState) .CombineLatest(updatesWithHistory, (transientState, histState) => { // Check if the highlighted prop still exists var highlightedProp = transientState.HighlightedProp; var isHighlightedItemDeleted = highlightedProp.IsJust && !histState.Props.ContainsKey(highlightedProp.Value); if (isHighlightedItemDeleted) { return(transientState.HighlightProp(Maybe.Nothing <PropId>())); } return(transientState); }); var updates = updatesWithHistory.CombineLatest( transientUpdates, (histState, transientState) => new CourseEditorState(histState, transientState)) .DistinctUntilChanged() .Replay(1); updates.Connect(); updatesWithHistory.Connect(); return(updates); }