public NeuronViewModel(Neuron neuron, string avatarUrl, INeuronQueryService neuronQueryService) { this.Neuron = neuron; this.Tag = neuron.Tag; this.avatarUrl = avatarUrl; this.neuronQueryService = neuronQueryService; }
public NeuronTreePaneViewModel(INeuronApplicationService neuronApplicationService = null, INeuronQueryService neuronQueryService = null, INotificationApplicationService notificationApplicationService = null, IStatusService statusService = null, IDialogService dialogService = null, IOriginService originService = null) { this.dialogService = dialogService ?? Locator.Current.GetService <IDialogService>(); this.neuronApplicationService = neuronApplicationService ?? Locator.Current.GetService <INeuronApplicationService>(); this.neuronQueryService = neuronQueryService ?? Locator.Current.GetService <INeuronQueryService>(); this.notificationApplicationService = notificationApplicationService ?? Locator.Current.GetService <INotificationApplicationService>(); this.statusService = statusService ?? Locator.Current.GetService <IStatusService>(); this.originService = originService ?? Locator.Current.GetService <IOriginService>(); this.statusService.WhenPropertyChanged(s => s.Message) .Subscribe(s => this.StatusMessage = s.Sender.Message); bool DefaultPredicate(Node <Neuron, int> node) => node.IsRoot; var cache = new SourceCache <Neuron, int>(x => x.UIId); this.AddCommand = ReactiveCommand.Create <object>(async(parameter) => await this.OnAddClicked(cache, parameter)); this.SetRegionCommand = ReactiveCommand.Create <object>(async(parameter) => await this.OnSetRegionIdClicked(parameter)); this.ReloadCommand = ReactiveCommand.Create(async() => await this.OnReloadClicked(cache)); this.cleanUp = cache.AsObservableCache().Connect() .TransformToTree(child => child.CentralUIId, Observable.Return((Func <Node <Neuron, int>, bool>)DefaultPredicate)) .Transform(e => e.Item.Type == RelativeType.Postsynaptic ? (NeuronViewModelBase)(new PostsynapticViewModel(this, e.Item.Tag, e, cache)) : (NeuronViewModelBase)(new PresynapticViewModel(this, e.Item.Tag, e, cache))) .Bind(out this.children) .DisposeMany() .Subscribe(); this.Target = null; this.Loading = false; this.IconSourcePath = @"pack://application:,,,/d23-wpf;component/images/hierarchy.ico"; }
public MainViewModel( ISettingsService settingsService, IDialogService dialogService, INavigationService <ViewModelBase> navigationService, IMessageService messageService, INeuronApplicationService neuronApplicationService, INeuronQueryService neuronQueryService ) : base(settingsService, dialogService, navigationService) { this.messageService = messageService; this.neuronApplicationService = neuronApplicationService; this.neuronQueryService = neuronQueryService; this.Loaded = false; this.IsAxonVisible = false; this.errors = string.Empty; this.PropertyChanged += this.MainViewModel_PropertyChanged; MessagingCenter.Subscribe <MainViewModel>(this, MessageKeys.NeuronSaved, async sender => { // if this is a target of the saved neuron if (sender.Axon.Any(t => t.TargetId == this.neuronId)) { Thread.Sleep(MainViewModel.GraphWaitInterval); // reload so dendrites are refreshed await this.LoadAsync(); } }); }
internal async static Task <bool> PromptSimilarExists(INeuronQueryService queryService, IDialogService dialogService, string avatarUrl, object owner, string result) { return(!(await queryService.GetNeurons(avatarUrl, neuronQuery: new NeuronQuery() { TagContains = new string[] { result } })).Any() || (await dialogService.ShowDialogYesNo("Other Neuron(s) containing a similar Tag value already exists. Are you sure you wish to continue?", owner, out DialogResult yesno2)).GetValueOrDefault()); }
public NeuronModule(INeuronQueryService neuronQueryService) : base("/cortex/neurons") { // use this from clients instead of neurongraphclient since latter is eventually consistent // returned version can be used by client to check if change is applicable this.Get("/{neuronId}", async(parameters) => new TextResponse(JsonConvert.SerializeObject( await neuronQueryService.GetNeuronById(parameters.neuronId)) ) ); }
public SelectViewModel( ISettingsService settingsService, IDialogService dialogService, INavigationService <ViewModelBase> navigationService, INeuronQueryService neuronQueryService ) : base(settingsService, dialogService, navigationService) { this.neuronQueryService = neuronQueryService; this.searchValue = string.Empty; }
public DialogSelectNeuronsViewModel(string message, string avatarUrl, bool allowMultiSelect, INeuronQueryService neuronQueryService = null) : base(message) { this.avatarUrl = avatarUrl; this.AllowMultiSelect = allowMultiSelect; this.neuronQueryService = neuronQueryService ?? Locator.Current.GetService <INeuronQueryService>(); var list = new SourceList <Neuron>(); this.ReloadCommand = ReactiveCommand.Create(async() => await this.OnReloadClicked(list)); this.SelectCommand = ReactiveCommand.Create(this.OnSelectedClicked); this.UserDialogResult = null; this.cleanUp = list.AsObservableList().Connect() .Bind(out this.neurons) .DisposeMany() .Subscribe(); }
public NeuronModule(INeuronQueryService queryService, IEventStoreApplicationService eventStoreApplicationService) : base("/cortex/neurons") { this.Get("", async(parameters) => { return(await NeuronModule.ProcessRequest(async() => { var nv = await queryService.GetNeurons(NeuronModule.ParseNeuronQueryOrEmpty(this.Request.Url.Query), NeuronModule.GetUserId(this.Request)); return new TextResponse(JsonConvert.SerializeObject(nv)); } )); } ); this.Get("/{neuronid:guid}", async(parameters) => { return(await NeuronModule.ProcessRequest(async() => { var nv = await queryService.GetNeuronById(parameters.neuronid, NeuronModule.ParseNeuronQueryOrEmpty(this.Request.Url.Query), NeuronModule.GetUserId(this.Request)); return new TextResponse(JsonConvert.SerializeObject(nv)); } )); } ); this.Get("/{centralid:guid}/relatives", async(parameters) => { return(await NeuronModule.ProcessRequest(async() => { var nv = await queryService.GetNeurons( parameters.centralid, NeuronModule.ParseNeuronQueryOrEmpty(this.Request.Url.Query), NeuronModule.GetUserId(this.Request) ); return new TextResponse(JsonConvert.SerializeObject(nv)); } )); } ); this.Get("/{centralid:guid}/relatives/{neuronid:guid}", async(parameters) => { return(await NeuronModule.ProcessRequest(async() => { var nv = await queryService.GetNeuronById( parameters.neuronid, parameters.centralid, NeuronModule.ParseNeuronQueryOrEmpty(this.Request.Url.Query), NeuronModule.GetUserId(this.Request) ); return new TextResponse(JsonConvert.SerializeObject(nv)); } )); } ); this.Get("/{aggregateid:guid}/events", async(parameters) => { return(await NeuronModule.ProcessRequest(async() => { // TODO: validate if guid represents a neuron var nv = await eventStoreApplicationService.Get( parameters.aggregateid, 0 ); return new TextResponse(JsonConvert.SerializeObject(nv)); } )); } ); }
internal async static Task <bool> CreateRelative(Func <Task <string> > tagRetriever, Func <object, Task <string[]> > terminalParametersRetriever, object owner, IDialogService dialogService, INeuronQueryService neuronQueryService, INeuronApplicationService neuronApplicationService, ITerminalApplicationService terminalApplicationService, IStatusService statusService, string avatarUrl, string regionId, string targetNeuronId, RelativeType relativeType) { bool result = false; await Neurons.Helper.SetStatusOnComplete(async() => { bool stat = false; var tag = await tagRetriever(); if (!string.IsNullOrEmpty(tag) && await Neurons.Helper.PromptSimilarExists(neuronQueryService, dialogService, avatarUrl, owner, tag)) { string[] tps = await terminalParametersRetriever(owner); var presynapticNeuronId = string.Empty; var postsynapticNeuronId = string.Empty; var newNeuronId = string.Empty; if (relativeType == RelativeType.Presynaptic) { newNeuronId = presynapticNeuronId = Guid.NewGuid().ToString(); postsynapticNeuronId = targetNeuronId; } else if (relativeType == RelativeType.Postsynaptic) { presynapticNeuronId = targetNeuronId; newNeuronId = postsynapticNeuronId = Guid.NewGuid().ToString(); } await neuronApplicationService.CreateNeuron( avatarUrl, newNeuronId, tag, regionId ); await terminalApplicationService.CreateTerminal( avatarUrl, Guid.NewGuid().ToString(), presynapticNeuronId, postsynapticNeuronId, (neurUL.Cortex.Common.NeurotransmitterEffect) int.Parse(tps[0]), float.Parse(tps[1]) ); result = true; stat = true; } return(stat); }, $"{relativeType.ToString()} created successfully.", statusService, $"{relativeType.ToString()} creation cancelled." ); return(result); }
internal async static Task <Neuron> CreateNeuron(Func <Task <string> > tagRetriever, object owner, IDialogService dialogService, INeuronQueryService neuronQueryService, INeuronApplicationService neuronApplicationService, INotificationApplicationService notificationApplicationService, IStatusService statusService, string avatarUrl, string regionId) { Neuron result = null; await Neurons.Helper.SetStatusOnComplete(async() => { bool stat = false; bool addingOwner = false; var shouldAddOwner = (await notificationApplicationService.GetNotificationLog(avatarUrl, string.Empty)).NotificationList.Count == 0; if (shouldAddOwner && (await dialogService.ShowDialogYesNo("This Avatar needs to be initialized with an Owner Neuron. Do you wish to continue by creating one?", owner, out DialogResult yesno)).GetValueOrDefault()) { addingOwner = true; } if (!shouldAddOwner || addingOwner) { string tag = await tagRetriever(); if (!string.IsNullOrEmpty(tag) && await Neurons.Helper.PromptSimilarExists(neuronQueryService, dialogService, avatarUrl, owner, tag) ) { Neuron n = new Neuron { Tag = tag, UIId = Guid.NewGuid().GetHashCode(), Id = Guid.NewGuid().ToString(), Version = 1, }; await neuronApplicationService.CreateNeuron( avatarUrl, n.Id, n.Tag, regionId ); result = n; stat = true; } } return(stat); }, "Neuron created successfully.", statusService, "Neuron creation cancelled." ); return(result); }
public PostsynapticViewModel(IAvatarViewer host, string tag, Node <Neuron, int> node, SourceCache <Neuron, int> cache, NeuronViewModelBase parent = null, INeuronApplicationService neuronApplicationService = null, INeuronQueryService neuronQueryService = null, ITerminalApplicationService terminalApplicationService = null, IExtendedSelectionService selectionService = null) : base(host, node, cache, parent, neuronApplicationService, neuronQueryService, terminalApplicationService, selectionService) { this.Tag = tag; }
protected NeuronViewModelBase(IAvatarViewer host, Node <Neuron, int> node, SourceCache <Neuron, int> cache, NeuronViewModelBase parent = null, INeuronApplicationService neuronApplicationService = null, INeuronQueryService neuronQueryService = null, ITerminalApplicationService terminalApplicationService = null, IExtendedSelectionService selectionService = null, IExtendedSelectionService highlightService = null, IStatusService statusService = null, IDialogService dialogService = null) { this.host = host; this.Id = node.Key; this.Parent = parent; this.SetNeuron(node.Item); this.ReloadCommand = ReactiveCommand.Create(async() => await this.OnReload(cache)); this.ReloadExpandCommand = ReactiveCommand.Create(async() => { await this.OnReload(cache); this.IsExpanded = true; }); this.AddPostsynapticCommand = ReactiveCommand.Create <object>(async(parameter) => await this.OnAddPostsynaptic(cache, parameter)); this.AddPresynapticCommand = ReactiveCommand.Create <object>(async(parameter) => await this.OnAddPresynaptic(cache, parameter)); this.DeleteCommand = ReactiveCommand.Create <object>(async(parameter) => await this.OnDeleteClicked(cache, parameter)); this.neuronApplicationService = neuronApplicationService ?? Locator.Current.GetService <INeuronApplicationService>(); this.neuronQueryService = neuronQueryService ?? Locator.Current.GetService <INeuronQueryService>(); this.terminalApplicationService = terminalApplicationService ?? Locator.Current.GetService <ITerminalApplicationService>(); this.selectionService = selectionService ?? Locator.Current.GetService <IExtendedSelectionService>(SelectionContract.Select.ToString()); this.highlightService = highlightService ?? Locator.Current.GetService <IExtendedSelectionService>(SelectionContract.Highlight.ToString()); this.statusService = statusService ?? Locator.Current.GetService <IStatusService>(); this.dialogService = dialogService ?? Locator.Current.GetService <IDialogService>(); var childrenLoader = new Lazy <IDisposable>(() => node.Children.Connect() .Transform(e => e.Item.Type == RelativeType.Postsynaptic ? (NeuronViewModelBase)(new PostsynapticViewModel(this.host, e.Item.Tag, e, cache, this)) : (NeuronViewModelBase)(new PresynapticViewModel(this.host, e.Item.Tag, e, cache, this))) .Bind(out this.children) .DisposeMany() .Subscribe() ); var shouldExpand = node.IsRoot ? Observable.Return(true) : Parent.Value.WhenValueChanged(t => t.IsExpanded); var expander = shouldExpand .Where(isExpanded => isExpanded) .Take(1) .Subscribe(_ => { var x = childrenLoader.Value; }); var childrenCount = node.Children.CountChanged .Select(count => { if (count == 0) { return("0 Synapses"); } else { return($"{node.Children.Items.Count(n => n.Item.Type == RelativeType.Postsynaptic)} Postsynaptic; " + $"{node.Children.Items.Count(n => n.Item.Type == RelativeType.Presynaptic)} Presynaptic"); } }) .Subscribe(text => this.ChildrenCountText = text); var changeTag = this.WhenPropertyChanged(p => p.Tag, false) .Subscribe(async(x) => await this.OnNeuronTagChanged(cache, x)); var selector = this.WhenPropertyChanged(p => p.IsSelected) .Where(p => p.Value) .Subscribe(x => { this.selectionService.SetSelectedComponents(new object[] { x.Sender }); this.host.Target = NeuronViewModelBase.ConvertNeuronViewModelToEditorNeuron((NeuronViewModelBase)x.Sender); }); var highlighter = this.highlightService.WhenPropertyChanged(a => a.SelectedComponents) .Subscribe(p => { if (p.Sender.SelectedComponents != null) { var selection = p.Sender.SelectedComponents.OfType <object>().ToArray(); if (selection.Count() > 0 && selection[0] is string) { if (selection.Count() < 2) { this.IsHighlighted = this.NeuronId == p.Sender.PrimarySelection.ToString(); } else { this.IsHighlighted = this.NeuronId == p.Sender.PrimarySelection.ToString() && this.TerminalId == selection[1].ToString(); } } } } ); this.cleanUp = Disposable.Create(() => { expander.Dispose(); childrenCount.Dispose(); if (childrenLoader.IsValueCreated) { childrenLoader.Value.Dispose(); } changeTag.Dispose(); selector.Dispose(); highlighter.Dispose(); }); }
public EditorToolViewModel(Workspace workspace = null, INeuronApplicationService neuronApplicationService = null, INeuronQueryService neuronQueryService = null, INotificationApplicationService notificationApplicationService = null, ITerminalApplicationService terminalApplicationService = null, IStatusService statusService = null, IDialogService dialogService = null) : base("Editor") { this.dialogService = dialogService ?? Locator.Current.GetService <IDialogService>(); this.neuronApplicationService = neuronApplicationService ?? Locator.Current.GetService <INeuronApplicationService>(); this.neuronQueryService = neuronQueryService ?? Locator.Current.GetService <INeuronQueryService>(); this.notificationApplicationService = notificationApplicationService ?? Locator.Current.GetService <INotificationApplicationService>(); this.terminalApplicationService = terminalApplicationService ?? Locator.Current.GetService <ITerminalApplicationService>(); this.statusService = statusService ?? Locator.Current.GetService <IStatusService>(); this.workspace = workspace ?? Locator.Current.GetService <Workspace>(); this.EditorState = EditorStateValue.Browse; this.TargetDraft = new EditorNeuronViewModel(); this.Target = null; this.InitDetailsSection(); this.NewCommand = ReactiveCommand.Create( () => this.OnNewClicked(), this.WhenAnyValue( vm => vm.EditorState, vm => vm.AvatarUrl, vm => vm.RegionName, (esv, au, ln) => esv == EditorStateValue.Browse && !string.IsNullOrEmpty(au) && !string.IsNullOrEmpty(ln) ) ); this.SaveCommand = ReactiveCommand.Create <object>( async(parameter) => await this.OnSaveClicked(parameter), Observable.CombineLatest( this.WhenAnyValue( vm => vm.EditorState, es => es == EditorStateValue.New || es == EditorStateValue.Edit ), this.IsValid(), (esValid, dataValid) => esValid && dataValid ) ); this.CancelCommand = ReactiveCommand.Create( () => this.OnCancelClicked(), this.WhenAnyValue( vm => vm.EditorState, es => es == EditorStateValue.New || es == EditorStateValue.Edit ) ); this.EditCommand = ReactiveCommand.Create( () => this.OnEditClicked(), this.WhenAnyValue( vm => vm.EditorState, vm => vm.AvatarUrl, vm => vm.RegionName, vm => vm.Target, (esv, au, ln, t) => esv == EditorStateValue.Browse && !string.IsNullOrEmpty(au) && !string.IsNullOrEmpty(ln) && t != null ) ); this.SelectCommand = ReactiveCommand.Create <object>( async(parameter) => await this.OnSelectClicked(parameter), this.WhenAnyValue( vm => vm.EditorState, vm => vm.NewMode, (es, nm) => es == EditorStateValue.New && nm == NewModeValue.Link ) ); this.WhenAnyValue(vm => vm.statusService.Message) .Subscribe(m => this.StatusMessage = m); this.WhenAnyValue(vm => vm.workspace.ActiveDocument) .Where(ad => ad is IAvatarViewer) .Subscribe(ad => this.AvatarViewer = (IAvatarViewer)ad); (this).WhenAnyValue(vm => vm.AvatarViewer) .Where(av => this.EditorState == EditorStateValue.Browse) .Subscribe(av => this.UpdateFromViewer(av)); this.WhenAnyValue(vm => vm.AvatarViewer.Target) .Where(lt => this.EditorState == EditorStateValue.Browse) .Subscribe(lt => this.Target = lt); this.WhenAnyValue(vm => vm.Target) .Subscribe(t => { if (t == null) { this.TargetDraft.Init(); } else { this.TargetDraft.Id = t.Id; this.TargetDraft.Tag = t.Tag; this.TargetDraft.Effect = t.Effect; this.TargetDraft.Strength = t.Strength; this.TargetDraft.RelativeType = t.RelativeType; this.TargetDraft.RegionId = t.RegionId; this.TargetDraft.RegionName = t.RegionName; this.TargetDraft.Version = t.Version; } }); this.WhenAnyValue(vm => vm.TargetDraft.RelativeType) .Where(rt => rt == null) .Subscribe(rt => this.TargetDraft.Effect = (NeurotransmitterEffect?)(this.TargetDraft.Strength = null)); this.WhenAnyValue(vm => vm.AvatarViewer.AvatarUrl) .Where(au => this.EditorState == EditorStateValue.Browse) .Subscribe(au => this.AvatarUrl = au); this.WhenAnyValue(vm => vm.AvatarViewer.RegionId) .Where(li => this.EditorState == EditorStateValue.Browse) .Subscribe(li => this.RegionId = li); this.WhenAnyValue(vm => vm.AvatarViewer.RegionName) .Where(ln => this.EditorState == EditorStateValue.Browse) .Subscribe(ln => this.RegionName = ln); this.WhenAnyValue(vm => vm.EditorState, vm => vm.TargetDraft.RelativeType) .Select(x => x.Item1 == EditorStateValue.New && x.Item2 != null) .ToPropertyEx(this, vm => vm.AreTerminalParametersEditable); this.WhenAnyValue(vm => vm.EditorState, vm => vm.NewMode) .Select(x => x.Item2 != ViewModels.NewModeValue.NotSet && x.Item2 != ViewModels.NewModeValue.Neuron && x.Item1 == ViewModels.EditorStateValue.New) .ToPropertyEx(this, vm => vm.IsRelativeTypeEditable); this.WhenAnyValue(vm => vm.EditorState) .Where(es => es == EditorStateValue.Browse) .Subscribe(es => { this.NewModes = new NewModeValue[0]; this.NewMode = NewModeValue.NotSet; this.UpdateFromViewer(this.AvatarViewer); }); this.WhenAnyValue(vm => vm.NewMode) .Subscribe(nm => { if (nm == NewModeValue.Neuron) { this.TargetDraft.RelativeType = null; } if (nm != NewModeValue.Link) { this.TargetDraft.LinkCandidates = null; } else { this.TargetDraft.Tag = string.Empty; } }); this.NewEditTagRule = this.ValidationRule( _ => this.WhenAnyValue( vm => vm.EditorState, vm => vm.NewMode, vm => vm.TargetDraft.Tag, (es, nm, t) => es == EditorStateValue.Browse || nm == NewModeValue.Link || !string.IsNullOrWhiteSpace(t) ), (vm, state) => !state ? "Tag must neither be null, empty, nor consist of white-space characters." : string.Empty ); this.NewLinkLinkCandidatesRule = this.ValidationRule( _ => this.WhenAnyValue( vm => vm.EditorState, vm => vm.NewMode, vm => vm.TargetDraft.LinkCandidates, (es, nm, lcs) => es != EditorStateValue.New || nm != NewModeValue.Link || (lcs != null && lcs.Count() > 0) ), (vm, state) => !state ? "Must select at least one Link Candidate." : string.Empty ); this.NewRelativeTypeRule = this.ValidationRule( _ => this.WhenAnyValue( vm => vm.EditorState, vm => vm.NewMode, vm => vm.TargetDraft.RelativeType, (es, nm, rt) => es != EditorStateValue.New || nm == NewModeValue.Neuron || (rt != null && rt != RelativeType.NotSet) ), (vm, state) => !state ? "Relative Type must be either Postsynaptic or Presynaptic." : string.Empty ); this.RelativeEffectRule = this.ValidationRule( _ => this.WhenAnyValue( vm => vm.EditorState, vm => vm.NewMode, vm => vm.TargetDraft.Effect, (es, nm, e) => es != EditorStateValue.New || nm == NewModeValue.Neuron || (e != null && e != NeurotransmitterEffect.NotSet) ), (vm, state) => !state ? "Effect must be either Excite or Inhibit." : string.Empty ); this.RelativeStrengthRule = this.ValidationRule( _ => this.WhenAnyValue( vm => vm.EditorState, vm => vm.NewMode, vm => vm.TargetDraft.Strength, (es, nm, s) => es != EditorStateValue.New || nm == NewModeValue.Neuron || (s != null && s > 0) ), (vm, state) => !state ? "Strength must be greater than zero." : string.Empty ); // TODO: this.IconSourcePath = @"pack://application:,,,/d23-wpf;component/images/wrench.ico"; }
public GraphModule(INeuronQueryService neuronQueryService, ITerminalQueryService terminalQueryService) : base("/cortex/graph") { this.Get("/neurons", async(parameters) => { return(await GraphModule.ProcessRequest(async() => { var nv = await neuronQueryService.GetNeurons(GraphModule.ExtractQuery(this.Request.Query)); return new TextResponse(JsonConvert.SerializeObject(nv)); } )); } ); this.Get("/neurons/{neuronid:guid}", async(parameters) => { return(await GraphModule.ProcessRequest(async() => { var nv = await neuronQueryService.GetNeuronById( parameters.neuronid, GraphModule.ExtractQuery(this.Request.Query) ); return new TextResponse(JsonConvert.SerializeObject(nv)); } )); } ); this.Get("/neurons/{centralid:guid}/relatives", async(parameters) => { return(await GraphModule.ProcessRequest(async() => { var nv = await neuronQueryService.GetNeurons( parameters.centralid, GraphModule.ExtractQuery(this.Request.Query) ); return new TextResponse(JsonConvert.SerializeObject(nv)); } )); } ); this.Get("/neurons/{centralid:guid}/relatives/{neuronid:guid}", async(parameters) => { return(await GraphModule.ProcessRequest(async() => { var nv = await neuronQueryService.GetNeuronById( parameters.neuronid, parameters.centralid, GraphModule.ExtractQuery(this.Request.Query) ); return new TextResponse(JsonConvert.SerializeObject(nv)); } )); } ); this.Get("/terminals/{terminalid:guid}", async(parameters) => { return(await GraphModule.ProcessRequest(async() => { var nv = await terminalQueryService.GetTerminalById( parameters.terminalid, GraphModule.ExtractQuery(this.Request.Query) ); return new TextResponse(JsonConvert.SerializeObject(nv)); } )); } ); }