• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2022 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import m from 'mithril';
16
17import {globals} from './globals';
18import {STAR} from './icons';
19
20import {
21  arrayOf,
22  bool,
23  record,
24  runValidator,
25  str,
26  ValidatedType,
27} from '../controller/validators';
28import {assertTrue} from '../base/logging';
29import {Icon} from './widgets/icon';
30import {runAnalyzeQuery} from './analyze_page';
31
32const QUERY_HISTORY_KEY = 'queryHistory';
33
34export class QueryHistoryComponent implements m.ClassComponent {
35  view(): m.Child {
36    const unstarred: HistoryItemComponentAttrs[] = [];
37    const starred: HistoryItemComponentAttrs[] = [];
38    for (let i = queryHistoryStorage.data.length - 1; i >= 0; i--) {
39      const entry = queryHistoryStorage.data[i];
40      const arr = entry.starred ? starred : unstarred;
41      arr.push({index: i, entry});
42    }
43    return m(
44        '.query-history',
45        m('header.overview',
46          `Query history (${queryHistoryStorage.data.length} queries)`),
47        starred.map((attrs) => m(HistoryItemComponent, attrs)),
48        unstarred.map((attrs) => m(HistoryItemComponent, attrs)));
49  }
50}
51
52export interface HistoryItemComponentAttrs {
53  index: number;
54  entry: QueryHistoryEntry;
55}
56
57export class HistoryItemComponent implements
58    m.ClassComponent<HistoryItemComponentAttrs> {
59  view(vnode: m.Vnode<HistoryItemComponentAttrs>): m.Child {
60    const query = vnode.attrs.entry.query;
61    return m(
62        '.history-item',
63        m('.history-item-buttons',
64          m(
65              'button',
66              {
67                onclick: () => {
68                  queryHistoryStorage.setStarred(
69                      vnode.attrs.index, !vnode.attrs.entry.starred);
70                  globals.rafScheduler.scheduleFullRedraw();
71                },
72              },
73              m(Icon, {icon: STAR, filled: vnode.attrs.entry.starred}),
74              ),
75          m('button',
76            {
77              onclick: () => runAnalyzeQuery(query),
78            },
79            m(Icon, {icon: 'play_arrow'})),
80          m('button',
81            {
82              onclick: () => {
83                queryHistoryStorage.remove(vnode.attrs.index);
84                globals.rafScheduler.scheduleFullRedraw();
85              },
86            },
87            m(Icon, {icon: 'delete'}))),
88        m('pre', query));
89  }
90}
91
92class HistoryStorage {
93  data: QueryHistory;
94  maxItems = 50;
95
96  constructor() {
97    this.data = this.load();
98  }
99
100  saveQuery(query: string) {
101    const items = this.data;
102    let firstUnstarred = -1;
103    let countUnstarred = 0;
104    for (let i = 0; i < items.length; i++) {
105      if (!items[i].starred) {
106        countUnstarred++;
107        if (firstUnstarred === -1) {
108          firstUnstarred = i;
109        }
110      }
111
112      if (items[i].query === query) {
113        // Query is already in the history, no need to save
114        return;
115      }
116    }
117
118    if (countUnstarred >= this.maxItems) {
119      assertTrue(firstUnstarred !== -1);
120      items.splice(firstUnstarred, 1);
121    }
122
123    items.push({query, starred: false});
124    this.save();
125  }
126
127  setStarred(index: number, starred: boolean) {
128    assertTrue(index >= 0 && index < this.data.length);
129    this.data[index].starred = starred;
130    this.save();
131  }
132
133  remove(index: number) {
134    assertTrue(index >= 0 && index < this.data.length);
135    this.data.splice(index, 1);
136    this.save();
137  }
138
139  private load(): QueryHistory {
140    const value = window.localStorage.getItem(QUERY_HISTORY_KEY);
141    if (value === null) {
142      return [];
143    }
144
145    return runValidator(queryHistoryValidator, JSON.parse(value)).result;
146  }
147
148  private save() {
149    window.localStorage.setItem(QUERY_HISTORY_KEY, JSON.stringify(this.data));
150  }
151}
152
153const queryHistoryEntryValidator = record({query: str(), starred: bool()});
154
155type QueryHistoryEntry = ValidatedType<typeof queryHistoryEntryValidator>;
156
157const queryHistoryValidator = arrayOf(queryHistoryEntryValidator);
158
159type QueryHistory = ValidatedType<typeof queryHistoryValidator>;
160
161export const queryHistoryStorage = new HistoryStorage();
162