Skip to content

Commit

Permalink
Close #7 case editor v1
Browse files Browse the repository at this point in the history
  • Loading branch information
driver-deploy-2 committed Sep 26, 2024
1 parent cbfdd73 commit 9d0ffd0
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 136 deletions.
80 changes: 62 additions & 18 deletions packages/gui/src/components/case-page.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,86 @@
import m from 'mithril';
import { Pages } from '../models';
import { MeiosisComponent, t } from '../services';
import { Chips } from 'mithril-materialized';
import { ChipData } from 'materialize-css';
import { CrimeScriptFilter, Pages } from '../models';
import { I18N, MeiosisComponent, routingSvc, t } from '../services';
import { TextInput } from 'mithril-materialized';
import { FormAttributes, LayoutForm, UIForm } from 'mithril-ui-form';
import { attributeFilterFormFactory, crimeScriptFilterFormFactory } from '../models/forms';

export const CasePage: MeiosisComponent = () => {
const tags: string[] = [];
let crimeScriptFilterForm: UIForm<CrimeScriptFilter>;

return {
oninit: ({
attrs: {
state: { model },
actions: { setPage },
},
}) => {
const { products = [], geoLocations = [], locations = [], cast = [], attributes = [], transports = [] } = model;
crimeScriptFilterForm = [
...crimeScriptFilterFormFactory(products, locations, geoLocations, 'search'),
...attributeFilterFormFactory(cast, attributes, transports, 'search'),
] as UIForm<CrimeScriptFilter>;
setPage(Pages.CASE);
},
view: ({ attrs: { state, actions } }) => {
const { caseTags = [], caseResults = [] } = state;
const { caseResults = [], caseFilter, crimeScriptFilter = {} as CrimeScriptFilter, model } = state;
const { update } = actions;
const data: ChipData[] = caseTags.map((tag) => ({ tag }));

return m('#case-page.row.case.page.markdown', [
return m('#case-page.row.case.page', [
m(LayoutForm, {
form: crimeScriptFilterForm,
obj: crimeScriptFilter,
onchange: () => {
actions.update({ crimeScriptFilter });
},
i18n: I18N,
} as FormAttributes<CrimeScriptFilter>),
m('.col.s12', [
m(Chips, {
data,
m(TextInput, {
label: 'Aangetroffen zaken',
iconName: 'search',
className: 'center-align',
onchange: (tags) => {
const caseTags = tags.map((tag) => tag.tag);
update({ caseTags });
initialValue: caseFilter,
onchange: (v) => {
// const caseTags = tags.map((tag) => tag.tag);
update({ caseFilter: v });
},
}),
]),
// caseResults.length > 0 &&
caseTags.length > 0 && m('.col.s12', m('h4', 'Meest waarschijnlijke acts')),
caseTags.map((tag) => {
return m('.col.s12', m('h5', tag));
}),
caseResults &&
m('.col.s12', [
m('p', t('HITS', caseResults.length)),
caseResults.length > 0 && [
m(
'ol',
caseResults.map(({ crimeScriptIdx, totalScore, acts }) =>
m(
'li',
`${model.crimeScripts[crimeScriptIdx].label} (score ${totalScore})`,
m(
'ul.browser-default',
acts.map(({ actIdx, phaseIdx, score }) =>
m(
'li',
m(
'a.truncate',
{
style: { cursor: 'pointer' },
href: routingSvc.href(Pages.CRIME_SCRIPT, `id=${model.crimeScripts[crimeScriptIdx].id}`),
onclick: () => {
actions.setLocation(model.crimeScripts[crimeScriptIdx].id, actIdx, phaseIdx);
},
},
`${actIdx >= 0 ? model.acts[actIdx].label : t('TEXT')} (score: ${score})`
)
)
)
)
)
)
),
],
]),
]);
},
};
Expand Down
44 changes: 13 additions & 31 deletions packages/gui/src/components/home-page.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import m from 'mithril';
import { Act, CrimeScript, Hierarchical, ID, Labeled, Pages, scriptIcon } from '../models';
import { CrimeScriptFilter, MeiosisComponent, routingSvc } from '../services';
import { Act, CrimeScript, CrimeScriptFilter, Hierarchical, ID, Labeled, Pages, scriptIcon } from '../models';
import { MeiosisComponent, routingSvc } from '../services';
import { FlatButton, uniqueId, Icon } from 'mithril-materialized';
import { I18N, t } from '../services/translations';
import { toCommaSeparatedList, toOptions } from '../utils';
import { toCommaSeparatedList } from '../utils';
import { FormAttributes, LayoutForm, UIForm } from 'mithril-ui-form';
import { crimeScriptFilterFormFactory } from '../models/forms';

export const HomePage: MeiosisComponent = () => {
const actLocations = (cs: CrimeScript, acts: Act[]) => {
Expand Down Expand Up @@ -35,12 +36,21 @@ export const HomePage: MeiosisComponent = () => {
return [...included, ...children, ...grandchildren];
};

let crimeScriptFilterForm: UIForm<CrimeScriptFilter>;

return {
oninit: ({
attrs: {
state: { model },
actions: { setPage },
},
}) => {
const { products = [], geoLocations = [], locations = [] } = model;
crimeScriptFilterForm = crimeScriptFilterFormFactory(
products,
locations,
geoLocations
) as UIForm<CrimeScriptFilter>;
setPage(Pages.HOME);
},
view: ({ attrs: { state, actions } }) => {
Expand All @@ -65,34 +75,6 @@ export const HomePage: MeiosisComponent = () => {
}
: (_cs: CrimeScript, _idx: number, _arr: CrimeScript[]) => true;

const crimeScriptFilterForm = [
{
id: 'productIds',
label: t('PRODUCTS', 2),
icon: 'filter_alt',
type: 'select',
multiple: true,
options: toOptions(products),
className: 'col s4',
},
{
id: 'locationIds',
label: t('LOCATIONS', 2),
type: 'select',
multiple: true,
options: toOptions(locations),
className: 'col s4',
},
{
id: 'geoLocationIds',
label: t('GEOLOCATIONS', 2),
type: 'select',
multiple: true,
options: toOptions(geoLocations),
className: 'col s4',
},
] as UIForm<CrimeScriptFilter>;

return m('#home-page.row.home.page', [
isAdmin &&
m(
Expand Down
13 changes: 11 additions & 2 deletions packages/gui/src/components/settings-page.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import m, { FactoryComponent } from 'mithril';
import { CrimeScript, ID, Pages, Act, Hierarchical, Labeled, DataModel } from '../models';
import {
CrimeScript,
ID,
Pages,
Act,
Hierarchical,
Labeled,
DataModel,
FlexSearchResult,
SearchScore,
} from '../models';
import { MeiosisComponent, routingSvc, t } from '../services';
import { deepCopy, FormAttributes, LayoutForm } from 'mithril-ui-form';
import { Collapsible, FlatButton, Tabs } from 'mithril-materialized';
import { attrForm, AttributeType } from '../models/forms';
import { TextInputWithClear } from './ui/text-input-with-clear';
import { FlexSearchResult, SearchScore } from '../services/flex-search';

type ItemType = 'cast' | 'attribute' | 'location' | 'geolocation' | 'transport' | 'product';

Expand Down
17 changes: 17 additions & 0 deletions packages/gui/src/models/data-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,23 @@ export type SearchResult = {
}[];
};

export enum SearchScore {
EXACT_MATCH = 3,
PARENT_MATCH = 2,
OTHER_MATCH = 1,
}

export type FlexSearchResult = [crimeScriptIdx: number, actIdx: number, phaseIdx: number, score: number];

export type CrimeScriptFilter = {
productIds: ID[];
geoLocationIds: ID[];
locationIds: ID[];
roleIds: ID[];
attributeIds: ID[];
transportIds: ID[];
};

export type ID = string;

export type Labeled = {
Expand Down
71 changes: 70 additions & 1 deletion packages/gui/src/models/forms.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { UIForm } from 'mithril-ui-form';
import { CrimeScript, Literature, Labeled, Hierarchical } from './data-model';
import { CrimeScript, Literature, Labeled, Hierarchical, CrimeScriptFilter } from './data-model';
import { toOptions } from '../utils';
import { t } from '../services';

export type AttributeType = 'cast' | 'attributes' | 'transports' | 'locations' | 'geoLocations' | 'products';
Expand Down Expand Up @@ -44,3 +45,71 @@ export const literatureForm = () =>
{ id: 'url', type: 'url', className: 'col s12', label: t('LINK') },
{ id: 'description', type: 'textarea', className: 'col s12', label: t('SUMMARY') },
] as UIForm<Partial<Literature>>;

export const crimeScriptFilterFormFactory = (
products: Array<Labeled & Hierarchical>,
locations: Array<Labeled & Hierarchical>,
geoLocations: Array<Labeled & Hierarchical>,
icon = 'filter_alt'
): UIForm<CrimeScriptFilter> =>
[
{
id: 'productIds',
label: t('PRODUCTS', 2),
icon,
type: 'select',
multiple: true,
options: toOptions(products),
className: 'col s6 m4',
},
{
id: 'locationIds',
label: t('LOCATIONS', 2),
type: 'select',
multiple: true,
options: toOptions(locations),
className: 'col s6 m4',
},
{
id: 'geoLocationIds',
label: t('GEOLOCATIONS', 2),
type: 'select',
multiple: true,
options: toOptions(geoLocations),
className: 'col s6 m4',
},
] as UIForm<CrimeScriptFilter>;

export const attributeFilterFormFactory = (
cast: Array<Labeled & Hierarchical>,
attributes: Array<Labeled & Hierarchical>,
transports: Array<Labeled & Hierarchical>,
icon = 'filter_alt'
) =>
[
{
id: 'roleIds',
label: t('CAST', 2),
icon,
type: 'select',
multiple: true,
options: toOptions(cast),
className: 'col s6 m4',
},
{
id: 'attributeIds',
label: t('ATTRIBUTES', 2),
type: 'select',
multiple: true,
options: toOptions(attributes),
className: 'col s6 m4',
},
{
id: 'transportIds',
label: t('TRANSPORTS', 2),
type: 'select',
multiple: true,
options: toOptions(transports),
className: 'col s6 m4',
},
] as UIForm<CrimeScriptFilter>;
19 changes: 2 additions & 17 deletions packages/gui/src/services/flex-search.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,8 @@
import { Service } from 'meiosis-setup/types';
import { State } from './meiosis';
import { DataModel, Hierarchical, ID, Labeled } from '../models';
import { DataModel, FlexSearchResult, Hierarchical, ID, Labeled, SearchScore } from '../models';
import { i18n } from './translations';

export type FlexSearchResult = [crimeScriptIdx: number, actIdx: number, phaseIdx: number, score: number];

export const tokenize = (text: string = '', stopwords: string[]): string[] => {
return text
.replace(/[^\w\s]/g, '') // Remove punctuation
.split(/\s+/) // Split into words
.map((word) => word.toLowerCase()) // Convert to lowercase
.filter((word) => word.length > 2 && !stopwords.includes(word)); // Exclude stopwords and empty strings
};

export enum SearchScore {
EXACT_MATCH = 3,
PARENT_MATCH = 2,
OTHER_MATCH = 1,
}
import { tokenize } from '../utils';

/**
* A flexible search solution:
Expand Down
6 changes: 3 additions & 3 deletions packages/gui/src/services/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,9 @@ export const messages = {
SEARCH: 'Search...',
SEARCH_TOOLTIP: 'Type / to search',
HITS: {
0: 'No results found',
1: '1 result found',
n: '{n} results found',
0: 'No results found.',
1: '1 result found:',
n: '{n} results found:',
},
SYNONYMS: 'Synonyms',
PARENTS: 'Parents',
Expand Down
6 changes: 3 additions & 3 deletions packages/gui/src/services/lang/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,9 @@ export const messagesNL: typeof messages = {
SEARCH: 'Zoek...',
SEARCH_TOOLTIP: 'Type / om te zoeken',
HITS: {
0: 'Geen resultaten gevonden',
1: '1 resultaat gevonden',
n: '{n} resultaten gevonden',
0: 'Geen resultaten gevonden.',
1: '1 resultaat gevonden:',
n: '{n} resultaten gevonden:',
},
SYNONYMS: 'Synoniemen',
PARENTS: 'Ouders',
Expand Down
Loading

0 comments on commit 9d0ffd0

Please sign in to comment.