aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbackend/histplorer.py31
-rw-r--r--backend/tests/test_histplorer.py10
-rw-r--r--src/App.vue2
-rw-r--r--src/components/SearchModal.vue7
-rw-r--r--src/components/SettingsModal.vue20
-rw-r--r--src/components/TimeLine.vue3
6 files changed, 46 insertions, 27 deletions
diff --git a/backend/histplorer.py b/backend/histplorer.py
index 988b69d..a553f88 100755
--- a/backend/histplorer.py
+++ b/backend/histplorer.py
@@ -16,7 +16,8 @@ Expected HTTP query parameters:
- event: With type=info, specifies the event title to get info for
- input: With type=sugg, specifies a search string to suggest for
- limit: With type=events or type=sugg, specifies the max number of results
-- ctg: With type=events or type=sugg, specifies an event category to restrict results to
+- ctgs: With type=events or type=sugg, specifies event categories to restrict results to
+ Interpreted as a period-separated list of category names (eg: person.place). An empty string is ignored.
"""
from typing import Iterable
@@ -171,7 +172,7 @@ def handleEventsReq(params: dict[str, str], dbCur: sqlite3.Cursor) -> EventRespo
print('INFO: Invalid scale value', file=sys.stderr)
return None
# Get event category
- ctg = params['ctg'] if 'ctg' in params else None
+ ctgs = params['ctgs'].split('.') if 'ctgs' in params else None
# Get incl value
try:
incl = int(params['incl']) if 'incl' in params else None
@@ -188,7 +189,7 @@ def handleEventsReq(params: dict[str, str], dbCur: sqlite3.Cursor) -> EventRespo
print(f'INFO: Invalid results limit {resultLimit}', file=sys.stderr)
return None
#
- events = lookupEvents(start, end, scale, ctg, incl, resultLimit, dbCur)
+ events = lookupEvents(start, end, scale, ctgs, incl, resultLimit, dbCur)
unitCounts = lookupUnitCounts(start, end, scale, dbCur)
return EventResponse(events, unitCounts)
def reqParamToHistDate(s: str):
@@ -202,7 +203,7 @@ def reqParamToHistDate(s: str):
return HistDate(None, int(m.group(1)))
else:
return HistDate(True, int(m.group(1)), int(m.group(2)), int(m.group(3)))
-def lookupEvents(start: HistDate | None, end: HistDate | None, scale: int, ctg: str | None,
+def lookupEvents(start: HistDate | None, end: HistDate | None, scale: int, ctgs: list[str] | None,
incl: int | None, resultLimit: int, dbCur: sqlite3.Cursor) -> list[Event]:
""" Looks for events within a date range, in given scale,
restricted by event category, an optional particular inclusion, and a result limit """
@@ -232,9 +233,9 @@ def lookupEvents(start: HistDate | None, end: HistDate | None, scale: int, ctg:
constraints.append('event_disp.unit < ?')
params.append(endUnit)
# Constrain by event category
- if ctg is not None:
- constraints.append('ctg = ?')
- params.append(ctg)
+ if ctgs is not None:
+ constraints.append('ctg IN (' + ','.join('?' * len(ctgs)) + ')')
+ params.extend(ctgs)
# Add constraints to query
query2 = query
if constraints:
@@ -341,24 +342,24 @@ def handleSuggReq(params: dict[str, str], dbCur: sqlite3.Cursor):
print(f'INFO: Invalid suggestion limit {resultLimit}', file=sys.stderr)
return None
#
- ctg = params['ctg'] if 'ctg' in params else None
- return lookupSuggs(searchStr, resultLimit, ctg, dbCur)
-def lookupSuggs(searchStr: str, resultLimit: int, ctg: str | None, dbCur: sqlite3.Cursor) -> SuggResponse:
+ ctgs = params['ctgs'].split('.') if 'ctgs' in params else None
+ return lookupSuggs(searchStr, resultLimit, ctgs, dbCur)
+def lookupSuggs(searchStr: str, resultLimit: int, ctgs: list[str] | None, dbCur: sqlite3.Cursor) -> SuggResponse:
""" For a search string, returns a SuggResponse describing search suggestions """
tempLimit = resultLimit + 1 # For determining if 'more suggestions exist'
query = 'SELECT title FROM events LEFT JOIN pop ON events.id = pop.id WHERE title LIKE ?'
- if ctg is not None:
- query += ' AND ctg = ?'
- query += f' ORDER BY pop.pop DESC LIMIT + {tempLimit}'
+ if ctgs is not None:
+ query += ' AND ctg IN (' + ','.join('?' * len(ctgs)) + ')'
+ query += f' ORDER BY pop.pop DESC LIMIT {tempLimit}'
suggs: list[str] = []
# Prefix search
- params = [searchStr + '%'] + ([ctg] if ctg is not None else [])
+ params = [searchStr + '%'] + (ctgs if ctgs is not None else [])
for (title,) in dbCur.execute(query, params):
suggs.append(title)
# If insufficient results, try substring search
if len(suggs) < tempLimit:
existing = set(suggs)
- params = ['%' + searchStr + '%'] + ([ctg] if ctg is not None else [])
+ params = ['%' + searchStr + '%'] + (ctgs if ctgs is not None else [])
for (title,) in dbCur.execute(query, params):
if title not in existing:
suggs.append(title)
diff --git a/backend/tests/test_histplorer.py b/backend/tests/test_histplorer.py
index fcaafb5..8f7e281 100644
--- a/backend/tests/test_histplorer.py
+++ b/backend/tests/test_histplorer.py
@@ -12,7 +12,7 @@ def initTestDb(dbFile: str) -> None:
'INSERT INTO events VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
{
(1, 'event one', 1900, None, None, None, 0, 'event'),
- (2, 'event two', 2452594, None, 2455369, None, 3, 'human'), # 2/11/2002 to 21/06/2010
+ (2, 'event two', 2452594, None, 2455369, None, 3, 'person'), # 2/11/2002 to 21/06/2010
(3, 'event three', 2448175, 2451828, None, None, 1, 'discovery'), # 10/10/1990 til 10/10/2000
(4, 'event four', 991206, None, 1721706, None, 2, 'event'), # 10/10/-2000 to 10/10/1
(5, 'event five', 2000, None, 2001, None, 0, 'event'),
@@ -116,7 +116,7 @@ class TestHandleReq(unittest.TestCase):
'discovery', 30, 0),
])
self.assertEqual(response.unitCounts, {1900: 2, 1990: 1, 2000: 1, 2001: 1})
- response = handleReq(self.dbFile, {'QUERY_STRING': 'type=events&range=.1999-11-27&scale=1&ctg=event'})
+ response = handleReq(self.dbFile, {'QUERY_STRING': 'type=events&range=.1999-11-27&scale=1&ctgs=event'})
self.assertEqual(response.events, [
Event(4, 'event four', HistDate(False, -2000, 10, 10), None, HistDate(False, 1, 10, 10), None,
'event', 20, 1000),
@@ -139,7 +139,7 @@ class TestHandleReq(unittest.TestCase):
def test_sugg_req(self):
response = handleReq(self.dbFile, {'QUERY_STRING': 'type=sugg&input=event t'})
self.assertEqual(response, SuggResponse(['event two', 'event three'], False))
- response = handleReq(self.dbFile, {'QUERY_STRING': 'type=sugg&input=o&ctg=event'})
- self.assertEqual(response, SuggResponse(['event four', 'event one'], False))
- response = handleReq(self.dbFile, {'QUERY_STRING': 'type=sugg&input=event&ctg=event&limit=1'})
+ response = handleReq(self.dbFile, {'QUERY_STRING': 'type=sugg&input=o&ctgs=event.person'})
+ self.assertEqual(response, SuggResponse(['event four', 'event two', 'event one'], False))
+ response = handleReq(self.dbFile, {'QUERY_STRING': 'type=sugg&input=event&ctgs=event&limit=1'})
self.assertEqual(response, SuggResponse(['event four'], True))
diff --git a/src/App.vue b/src/App.vue
index 70cd390..8ca2f95 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -230,6 +230,8 @@ async function onEventDisplay(
}
lastQueriedRange = [firstDate, lastDate];
let urlParams = new URLSearchParams({
+ // Note: Intentionally not filtering by event categories (would need category-sensitive
+ // unit count data to determine when enough events have been obtained)
type: 'events',
range: `${firstDate}.${lastDate}`,
scale: String(SCALES[scaleIdx]),
diff --git a/src/components/SearchModal.vue b/src/components/SearchModal.vue
index 65d2496..96d23bd 100644
--- a/src/components/SearchModal.vue
+++ b/src/components/SearchModal.vue
@@ -28,7 +28,7 @@
</template>
<script setup lang="ts">
-import {ref, computed, onMounted, onUnmounted, PropType} from 'vue';
+import {ref, computed, onMounted, PropType} from 'vue';
import SearchIcon from './icon/SearchIcon.vue';
import InfoIcon from './icon/InfoIcon.vue';
import {HistEvent, queryServer, EventInfoJson, jsonToEventInfo, SuggResponseJson} from '../lib';
@@ -93,6 +93,11 @@ async function onInput(){
input: input.value,
limit: String(store.searchSuggLimit),
});
+ // Check if any event categories are disabled
+ if (Object.values(store.ctgs).some((b: boolean) => !b)){
+ let visibleCtgs = Object.entries(store.ctgs).filter(([, enabled]) => enabled).map(([ctg, ]) => ctg);
+ urlParams.append('ctgs', visibleCtgs.join('.'));
+ }
// Code for querying server
pendingReqParams.value = urlParams;
pendingReqInput.value = input.value;
diff --git a/src/components/SettingsModal.vue b/src/components/SettingsModal.vue
index 1ccea2a..21ac7b8 100644
--- a/src/components/SettingsModal.vue
+++ b/src/components/SettingsModal.vue
@@ -9,18 +9,18 @@
<h2 class="font-bold md:text-xl text-center pt-1 md:pt-2 md:pb-1">Categories</h2>
<ul class="px-2 grid grid-cols-3">
<!-- Row 1 -->
- <li> <label> <input type="checkbox" v-model="store.ctgs.event"
+ <li> <label> <input type="checkbox" v-model="store.ctgs.event" :disabled="lastCtg == 'event'"
@change="onSettingChg('ctgs.event')"/> Event </label> </li>
- <li> <label> <input type="checkbox" v-model="store.ctgs.person"
+ <li> <label> <input type="checkbox" v-model="store.ctgs.person" :disabled="lastCtg == 'person'"
@change="onSettingChg('ctgs.person')"/> Person </label> </li>
- <li> <label> <input type="checkbox" v-model="store.ctgs.work"
+ <li> <label> <input type="checkbox" v-model="store.ctgs.work" :disabled="lastCtg == 'work'"
@change="onSettingChg('ctgs.work')"/> Work </label> </li>
<!-- Row 2 -->
- <li> <label> <input type="checkbox" v-model="store.ctgs.place"
+ <li> <label> <input type="checkbox" v-model="store.ctgs.place" :disabled="lastCtg == 'place'"
@change="onSettingChg('ctgs.place')"/> Place </label> </li>
- <li> <label> <input type="checkbox" v-model="store.ctgs.organism"
+ <li> <label> <input type="checkbox" v-model="store.ctgs.organism" :disabled="lastCtg == 'organism'"
@change="onSettingChg('ctgs.organism')"/> Organism </label> </li>
- <li> <label> <input type="checkbox" v-model="store.ctgs.discovery"
+ <li> <label> <input type="checkbox" v-model="store.ctgs.discovery" :disabled="lastCtg == 'discovery'"
@change="onSettingChg('ctgs.discovery')"/> Discovery </label> </li>
</ul>
</div>
@@ -88,6 +88,14 @@ const emit = defineEmits(['close']);
// Settings change handling
const saved = ref(false); // Set to true after a setting is saved
+const lastCtg = computed(() => { // When all but one category is disabled, names the remaining category
+ let enabledCtgs = Object.entries(store.ctgs).filter(([, enabled]) => enabled).map(([ctg, ]) => ctg);
+ if (enabledCtgs.length == 1){
+ return enabledCtgs[0];
+ } else {
+ return null;
+ }
+});
function onSettingChg(option: string){
store.save(option);
// Make 'Saved' indicator appear/animate
diff --git a/src/components/TimeLine.vue b/src/components/TimeLine.vue
index 13589d2..1583aef 100644
--- a/src/components/TimeLine.vue
+++ b/src/components/TimeLine.vue
@@ -444,6 +444,9 @@ const idToEvent = computed(() => { // Maps visible event IDs to HistEvents
if (!event.start.isEarlier(lastDate.value)){
break;
}
+ if ((store.ctgs as {[ctg: string]: boolean})[event.ctg] == false){
+ continue;
+ }
map.set(event.id, event);
}
return map;