Skip to content

Commit

Permalink
#7 created item details module and changed the home page service to w…
Browse files Browse the repository at this point in the history
…ikidata
  • Loading branch information
timofeysie committed Nov 13, 2020
1 parent c5e8147 commit 9684578
Show file tree
Hide file tree
Showing 24 changed files with 1,160 additions and 850 deletions.
1,623 changes: 812 additions & 811 deletions .firebase/hosting.ZGlzdA.cache

Large diffs are not rendered by default.

56 changes: 40 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ npm run electron:build // Build desktop app
npm run electron:run // Run app on electron
npm run electron:package // Package the app
http-server -p 8080 -c-1 dist // run the PWA (after a build)
firebase deploy
```

## Project brief
Expand Down Expand Up @@ -87,6 +88,8 @@ The categories directory can be the start of a feature directory which will hold

### Some previous notes on the pattern

ttps://en.wikipedia.org/api/rest_v1/page/summary/Basic_English#Word_lists

A service that uses the pattern might look like this:

```
Expand Down Expand Up @@ -119,8 +122,8 @@ component stores that contain the states used by a single component (not singlet

Proxy component with no biz logic can use the async pipe

```
<li *ngFor="let candidate of (store.state$ | async).candidates">
```html
<li *ngFor="let candidate of (store.state$ | async).candidates"></li>
```

Another article based on the above is [here](https://georgebyte.com/state-management-in-angular-with-observable-store-services/)
Expand All @@ -131,7 +134,7 @@ Create a categories component to view the list.

A category has the following properties:

```
```txt
category
language
wdt
Expand All @@ -140,7 +143,7 @@ wd

The language should be the setting from the i18n selector pre-existing in the app.  There are two predetermined categories to start:

```
```txt
name=fallacies
wdt=P31
wd=Q186150
Expand All @@ -154,26 +157,26 @@ This project has a hardwired category of "cognitive biases" which has a lot of t

The lists will need to have pagination, with the number of items per page configured in an options page. The initial categories list will be short, so it's OK to wait until the items lists to implement this, but be aware that this will be part of the state.

# Issue #4: Fetch a list of wikidata items for the selected category
## Issue #4: Fetch a list of wikidata items for the selected category

Create an Items component to display the list of items for a category.

Categories can be used to construct a sparql query can be created like this:

```
        SELECT ?${category} ?${category}Label ?${category}Description WHERE {
            SERVICE wikibase:label {
                bd:serviceParam wikibase:language "[AUTO_LANGUAGE],${language}".
            }
            ?${category} wdt:${wdt} wd:${wd}.
        }
        LIMIT 1000
```SQL
SELECT ?${category} ?${category}Label ?${category}Description WHERE {
    SERVICE wikibase:label {
         bd:serviceParam wikibase:language "[AUTO_LANGUAGE],${language}".
    }
    ?${category} wdt:${wdt} wd:${wd}.
}
LIMIT 1000
const url = wdk.sparqlQuery(sparql);
```

This will construct a url that will return a result with properties like this.

```
```json
"head":{
      "vars":[
         "fallacies",
Expand Down Expand Up @@ -220,9 +223,30 @@ Create a details page to show the details of an item selected.
An item can be used to get a detail page from Wikipedia.
Wikidata will also hold a list of languages available for each item.  This property can be used to get translated pages.

Detail pages also contain preamble icons with warnings which need to be captures and shown as collapsable icons under the description.
Detail pages also contain preamble icons with warnings which need to be captures and shown as collapsible icons under the description.
https://github.com/timofeysie/strumosa-pipe#the-items-api

I'm not sure about the routing for item details. It seems strange now to have categories as the root for it.

```txt
/categories/item-details/Q295150
```

The [Conchifolia details page](https://github.com/timofeysie/conchifolia/blob/master/my-dream-app/src/app/pages/detail/detail.page.ts) uses a backendApiService.getDetail(this.title,listLanguage, false) to get the details.

The only comment there is /api/detail/id/lang/leaveCaseAlone.

A sample item uri looks like this: http://www.wikidata.org/entity/Q295150

https://en.wikipedia.org/wiki/Ecological_fallacy

It could also look like this example from Conchifolia:
https://en.wikipedia.org/wiki/Actor-observer%20bias

Some reasons to get the wikidata page first is we get a list of available languages, and we get the exact url for the Wikipedia page. In the other projects, this was not a straight forward thing, as there were items that had different labels or different formats or would result in redirects. So to simplify that, parsing the wikidata page for the list of languages and Wikipedia uri links is a good idea.

Next, we used various Node server program to get around CORS issues for this. However, we don't want to have to maintain another app to do this. Also, we would have to pay for traffic if there ever was any. Using React Native was the only client that was able to handle the calls without issue.

## Issue #8: Create a form to enter a new category

This will just be a simple input to let the user enter a new category. It will end up being a SPARQL query such as 'list of <category>' where <category> is a plural word such as "cognitive biases" or "fallacies".
Expand Down Expand Up @@ -358,7 +382,7 @@ npm run serve:sw

To test the service worker in production the app must be hosted somewhere. Deployment from the master branch was done with Firebase like this:

```
```txt
m$ firebase init
######## #### ######## ######## ######## ### ###### ########
## ## ## ## ## ## ## ## ## ## ##
Expand Down
7 changes: 7 additions & 0 deletions src/app/core/interfaces/item-details.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface ItemDetails {
name: string;
label?: string;
language: string;
wdt: string;
wd: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ import { extract } from '@app/core';
import { ItemsContainerComponent } from './items/container/items-container.component';
import { CategoriesContainerComponent } from './categories/container/categories-list/categories-container.component';
import { AddCategoryContainerComponent } from './categories/container/add-category/add-category-container.component';
import { ItemDetailsContainerComponent } from './item-details/container/item-details/item-details-container.component';

const routes: Routes = [
// Module is lazy loaded, see app-routing.module.ts
{ path: '', component: CategoriesContainerComponent, data: { title: extract('Categories') } },
{ path: ':name/:wdt/:wd/:language/items', component: ItemsContainerComponent, data: { title: extract('items') } },
{ path: 'add', component: AddCategoryContainerComponent, data: { title: extract('items') } }
{ path: 'add', component: AddCategoryContainerComponent, data: { title: extract('items') } },
{
path: 'item-details/:qcode',
component: ItemDetailsContainerComponent,
data: { title: extract('items') }
}
];

@NgModule({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ import { IonicModule } from '@ionic/angular';
import { ItemsContainerComponent } from './items/container/items-container.component';
import { ItemsComponent } from './items/components/items.component';
import { ItemsListEndpoint } from './items/items.endpoint';

import { ItemDetailsContainerComponent } from './item-details/container/item-details/item-details-container.component';
import { ItemDetailsComponent } from './item-details/components/item-details/item-details.component';
import { ItemDetailsEndpoint } from './item-details/item-details.endpoint';

import { CategoryItemDetailsRoutingModule } from './category-item-details-routing.module';
import { CategoriesContainerComponent } from './categories/container/categories-list/categories-container.component';
import { CategoryComponent } from './categories/components/categories-list/category.component';
import { ReactiveFormsModule } from '@angular/forms';
import { CategoriesEndpoint } from './categories/categories.endpoint';

import { AddCategoryContainerComponent } from './categories/container/add-category/add-category-container.component';
import { AddCategoryComponent } from './categories/components/add-category/add-category.component';

Expand All @@ -22,9 +28,11 @@ import { AddCategoryComponent } from './categories/components/add-category/add-c
CategoryComponent,
ItemsContainerComponent,
ItemsComponent,
ItemDetailsContainerComponent,
ItemDetailsComponent,
AddCategoryContainerComponent,
AddCategoryComponent
],
providers: [ItemsListEndpoint, CategoriesEndpoint]
providers: [ItemsListEndpoint, CategoriesEndpoint, ItemDetailsEndpoint]
})
export class CategoryItemDetailsModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Type } from '@angular/core';
import { TestBed, async } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

import { CoreModule, HttpCacheService } from '@app/core';
import { QuoteService } from './quote.service';

describe('QuoteService', () => {
let quoteService: QuoteService;
let httpMock: HttpTestingController;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreModule, HttpClientTestingModule],
providers: [HttpCacheService, QuoteService]
});

quoteService = TestBed.get(QuoteService);
httpMock = TestBed.get(HttpTestingController as Type<HttpTestingController>);

const htttpCacheService = TestBed.get(HttpCacheService);
htttpCacheService.cleanCache();
});

afterEach(() => {
httpMock.verify();
});

describe('getRandomQuote', () => {
it('should return a random Chuck Norris quote', () => {
// Arrange
const mockQuote = { value: 'a random quote' };

// Act
const randomQuoteSubscription = quoteService.getRandomQuote({ category: 'toto' });

// Assert
randomQuoteSubscription.subscribe((quote: string) => {
expect(quote).toEqual(mockQuote.value);
});
httpMock.expectOne({}).flush(mockQuote);
});

it('should return a string in case of error', () => {
// Act
const randomQuoteSubscription = quoteService.getRandomQuote({ category: 'toto' });

// Assert
randomQuoteSubscription.subscribe((quote: string) => {
expect(typeof quote).toEqual('string');
expect(quote).toContain('Error');
});
httpMock.expectOne({}).flush(null, {
status: 500,
statusText: 'error'
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';

const routes = {
entities: (c: WikidataContext) => `/Special:EntityData/Q295150${c.qcode}.json`
};

export interface WikidataContext {
qcode: string;
}

@Injectable({
providedIn: 'root'
})
export class CategoryItemDetailsService {
constructor(private httpClient: HttpClient) {}

getRandomQuote(context: WikidataContext): Observable<string> {
return this.httpClient
.cache()
.get(routes.entities(context))
.pipe(
map((body: any) => body.value),
catchError(() => of('Error, could not load joke :-('))
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<ion-item color="primary">
Coming soon.
</ion-item>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ion-item {
cursor: pointer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Component, OnInit, Input } from '@angular/core';
import { ItemDetails } from '@app/core/interfaces/item-details';

@Component({
selector: 'item-details',
templateUrl: './item-details.component.html',
styleUrls: ['./item-details.component.scss']
})
export class ItemDetailsComponent implements OnInit {
@Input() itemDetails: ItemDetails;

constructor() {}

ngOnInit() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<ion-header>
<ion-toolbar color="primary">
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title><span translate>Categories</span></ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-card class="ion-text-center">
<ion-card-content>
<ion-list *ngFor="let category of (store.state$ | async).categories">
<categories-category [category]="category"></categories-category>
</ion-list>
</ion-card-content>
</ion-card>
</ion-content>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CategoriesContainerComponent } from './categories-container.component';

describe('CategoriesComponent', () => {
let component: CategoriesContainerComponent;
let fixture: ComponentFixture<CategoriesContainerComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [CategoriesContainerComponent]
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(CategoriesContainerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Component, OnInit } from '@angular/core';
import { categoriesList } from '@env/environment';
import { ItemDetailsStore } from '../../item-details-store';

@Component({
selector: 'app-categories',
templateUrl: './item-details-container.component.html',
styleUrls: ['./item-details-container.component.scss'],
providers: [ItemDetailsStore]
})
export class ItemDetailsContainerComponent implements OnInit {
constructor(public store: ItemDetailsStore) {}

ngOnInit() {
// this.store.fetchList();
console.log(' this.store', this.store);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ItemDetails } from '@app/core/interfaces/item-details';

const initItemDetails: ItemDetails[] = [];

export class ItemDetailsState {
itemDetails: ItemDetails[] = initItemDetails;
}
Loading

0 comments on commit 9684578

Please sign in to comment.