blob: 53823221b7354c2e91860f2c25aead411dfd008b [file] [log] [blame]
// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import m from 'mithril';
import {globals} from './globals';
import {STAR} from './icons';
import {
arrayOf,
bool,
record,
runValidator,
str,
ValidatedType,
} from '../controller/validators';
import {assertTrue} from '../base/logging';
import {Icon} from './widgets/icon';
import {runAnalyzeQuery} from './analyze_page';
const QUERY_HISTORY_KEY = 'queryHistory';
export class QueryHistoryComponent implements m.ClassComponent {
view(): m.Child {
const unstarred: HistoryItemComponentAttrs[] = [];
const starred: HistoryItemComponentAttrs[] = [];
for (let i = queryHistoryStorage.data.length - 1; i >= 0; i--) {
const entry = queryHistoryStorage.data[i];
const arr = entry.starred ? starred : unstarred;
arr.push({index: i, entry});
}
return m(
'.query-history',
m('header.overview',
`Query history (${queryHistoryStorage.data.length} queries)`),
starred.map((attrs) => m(HistoryItemComponent, attrs)),
unstarred.map((attrs) => m(HistoryItemComponent, attrs)));
}
}
export interface HistoryItemComponentAttrs {
index: number;
entry: QueryHistoryEntry;
}
export class HistoryItemComponent implements
m.ClassComponent<HistoryItemComponentAttrs> {
view(vnode: m.Vnode<HistoryItemComponentAttrs>): m.Child {
const query = vnode.attrs.entry.query;
return m(
'.history-item',
m('.history-item-buttons',
m(
'button',
{
onclick: () => {
queryHistoryStorage.setStarred(
vnode.attrs.index, !vnode.attrs.entry.starred);
globals.rafScheduler.scheduleFullRedraw();
},
},
m(Icon, {icon: STAR, filled: vnode.attrs.entry.starred}),
),
m('button',
{
onclick: () => runAnalyzeQuery(query),
},
m(Icon, {icon: 'play_arrow'})),
m('button',
{
onclick: () => {
queryHistoryStorage.remove(vnode.attrs.index);
globals.rafScheduler.scheduleFullRedraw();
},
},
m(Icon, {icon: 'delete'}))),
m('pre', query));
}
}
class HistoryStorage {
data: QueryHistory;
maxItems = 50;
constructor() {
this.data = this.load();
}
saveQuery(query: string) {
const items = this.data;
let firstUnstarred = -1;
let countUnstarred = 0;
for (let i = 0; i < items.length; i++) {
if (!items[i].starred) {
countUnstarred++;
if (firstUnstarred === -1) {
firstUnstarred = i;
}
}
if (items[i].query === query) {
// Query is already in the history, no need to save
return;
}
}
if (countUnstarred >= this.maxItems) {
assertTrue(firstUnstarred !== -1);
items.splice(firstUnstarred, 1);
}
items.push({query, starred: false});
this.save();
}
setStarred(index: number, starred: boolean) {
assertTrue(index >= 0 && index < this.data.length);
this.data[index].starred = starred;
this.save();
}
remove(index: number) {
assertTrue(index >= 0 && index < this.data.length);
this.data.splice(index, 1);
this.save();
}
private load(): QueryHistory {
const value = window.localStorage.getItem(QUERY_HISTORY_KEY);
if (value === null) {
return [];
}
return runValidator(queryHistoryValidator, JSON.parse(value)).result;
}
private save() {
window.localStorage.setItem(QUERY_HISTORY_KEY, JSON.stringify(this.data));
}
}
const queryHistoryEntryValidator = record({query: str(), starred: bool()});
type QueryHistoryEntry = ValidatedType<typeof queryHistoryEntryValidator>;
const queryHistoryValidator = arrayOf(queryHistoryEntryValidator);
type QueryHistory = ValidatedType<typeof queryHistoryValidator>;
export const queryHistoryStorage = new HistoryStorage();