Unnamed Fossil Project

Check-in [5dc54d9bcb]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Tighten up display, especially for small screens. Fix gold/yellow toggle. Use item ID's, get rid of numbering confusion when adding (and, I suppose, deleting) items to the list.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256:5dc54d9bcbcb3f7f9ceb5928b9fea2312992219d4cb53123418e52918c8cc1db
User & Date: vandys 2019-06-23 21:43:54
Context
2019-06-23
21:44
Ignore certs, if present check-in: c86da22d43 user: vandys tags: trunk
21:43
Tighten up display, especially for small screens. Fix gold/yellow toggle. Use item ID's, get rid of numbering confusion when adding (and, I suppose, deleting) items to the list. check-in: 5dc54d9bcb user: vandys tags: trunk
16:35
Span color filtering. Deal with online/offline. Quiet complaint if we abort our long poll. check-in: eef76568c0 user: vandys tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to css/shopr.css.

13
14
15
16
17
18
19

20

21
22

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
}
#scrlist2 > span, #scrlist2 > input, #scrlist2 > button {
 border: 3px solid black;
 border-radius: 6px;
}
#items {
 display: flex;

 flex-wrap: wrap;

}
#items > span {

 flex-grow: 0;
 width: 9%;
 text-align: center;
 border-bottom: 3px solid black;
}
.isel {
 flex-grow: 1 !important;
 width: 78% !important;
}
#items > span:nth-child(3n+1) {
 background: pink;
}
#items > span:nth-child(3n+2) {
 background: LightGreen;
}







>

>


>

|

|



|







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
}
#scrlist2 > span, #scrlist2 > input, #scrlist2 > button {
 border: 3px solid black;
 border-radius: 6px;
}
#items {
 display: flex;
 flex-direction: row;
 flex-wrap: wrap;
 flex-basis: 100%;
}
#items > span {
 padding: 0px;
 flex-grow: 0;
 width: 10%;
 text-align: center;
 border-bottom: 1px solid black;
}
.isel {
 flex-grow: 1 !important;
 width: 80% !important;
}
#items > span:nth-child(3n+1) {
 background: pink;
}
#items > span:nth-child(3n+2) {
 background: LightGreen;
}

Changes to etc/config.

3
4
5
6
7
8
9
10
11
12


#
service "Shopr"

# sqlite3 storage
db var/shopr.db

# Web interface
serve http
    port 8085










|
|
<
>
>
3
4
5
6
7
8
9
10
11

12
13
#
service "Shopr"

# sqlite3 storage
db var/shopr.db

# Web interface
serve https
    port 8095

    publicCert certs/server.crt
    privateKey certs/server.key

Changes to items.py.

1
2
3
4
5
6

7
8
9
10
11
12
13
..
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
..
68
69
70
71
72
73
74
75
76




77
78
79
80
81
82
83
84
85
..
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103




104
105
106



107
108
109
110
111
112
113
114
115
116



117
118
119
120
121
122
123



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#
# items.py
#	Represent the items on a shopping list
#
import json, threading
import sqlite3


class List(object):
    def __init__(self, approot, lid, isglob, name):
	self.approot = approot
	self.lid = lid
	self.isglob = isglob
	self.name = name
................................................................................
	self.items = []
	self.gen = 0
	self.waiters = set()

	# Load any existing state
	db = self.get_db()
	c = db.cursor()
	c.execute("select name,idx from items where list=?", (lid,))
	for tup in c:
	    iname,idx = tup

	    # Deleted items shouldn't be in the DB
	    assert idx >= 0

	    # New item, with the saved generation value
	    i = Item(self, idx, iname)
	    self.items.append(i)

	c.close()
	db.close()

    # Return a DB connection
    def get_db(self):
................................................................................
	    # Don't share deleted
	    if i.deleted():
		continue
	    items.append(i.for_json())
	return res

    # Update idx for the given item
    def set_idx(self, row, col):
	if (row < 0) or (row >= len(self.items)):




	    return False
	l = self.items[row]

	# Update item, which flags change to list
	l.assign(col)

	return True

    # Add an item to this list
................................................................................
    def new_item(self, name):
	# No dups, please
	ln = name.lower()
	for i in self.items:
	    if i.name.lower() == ln:
		return None

	# Create data locally
	i = Item(self, 0, name)

	# Put in DB
	db = self.get_db()
	c = db.cursor()
	c.execute("insert or replace into items values(?,?,?)",
	    (self.lid, name, 0));
	db.commit()




	c.close()
	db.close()




	# Ok
	self.items.append(i)
	self.next_gen()
	return i

class Item(object):
    def __init__(self, l, idx, name):

	# Our parent list
	self.parent = l




	# Current indexed state
	self.idx = idx

	# Item's string name
	self.name = name




    # JSON friendly rep of our state
    def for_json(self):
	assert self.idx != -1
	return {"name": self.name, "idx": self.idx}

    # Save ourselves to the DB
    def sync(self):
	db = self.parent.get_db()
	c = db.cursor()
	c.execute("insert or replace into items values(?,?,?) ",
	    (self.parent.lid, self.name, self.idx))
	db.commit()
	c.close()
	db.close()

    def assign(self, idx):
	# No change
	if self.idx == idx:






>







 







|

|





|







 







|
|
>
>
>
>

<







 







<
<
<



|


>
>
>
>



>
>
>






|



>
>
>







>
>
>



|





|
|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
..
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
..
69
70
71
72
73
74
75
76
77
78
79
80
81
82

83
84
85
86
87
88
89
..
92
93
94
95
96
97
98



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#
# items.py
#	Represent the items on a shopping list
#
import json, threading
import sqlite3
import pdb

class List(object):
    def __init__(self, approot, lid, isglob, name):
	self.approot = approot
	self.lid = lid
	self.isglob = isglob
	self.name = name
................................................................................
	self.items = []
	self.gen = 0
	self.waiters = set()

	# Load any existing state
	db = self.get_db()
	c = db.cursor()
	c.execute("select iid,name,idx from items where list=?", (lid,))
	for tup in c:
	    iid,iname,idx = tup

	    # Deleted items shouldn't be in the DB
	    assert idx >= 0

	    # New item, with the saved generation value
	    i = Item(self, iid, idx, iname)
	    self.items.append(i)

	c.close()
	db.close()

    # Return a DB connection
    def get_db(self):
................................................................................
	    # Don't share deleted
	    if i.deleted():
		continue
	    items.append(i.for_json())
	return res

    # Update idx for the given item
    def set_idx(self, iid, col):
	# Find this item ID
	for l in self.items:
	    if l.iid == iid:
		break
	else:
	    return False


	# Update item, which flags change to list
	l.assign(col)

	return True

    # Add an item to this list
................................................................................
    def new_item(self, name):
	# No dups, please
	ln = name.lower()
	for i in self.items:
	    if i.name.lower() == ln:
		return None




	# Put in DB
	db = self.get_db()
	c = db.cursor()
	c.execute("insert or replace into items values(NULL,?,?,?)",
	    (self.lid, name, 0));
	db.commit()

	# Note assigned new item ID for this
	iid = c.lastrowid

	c.close()
	db.close()

	# Create data locally
	i = Item(self, iid, 0, name)

	# Ok
	self.items.append(i)
	self.next_gen()
	return i

class Item(object):
    def __init__(self, l, iid, idx, name):

	# Our parent list
	self.parent = l

	# Unique DB ID
	self.iid = iid

	# Current indexed state
	self.idx = idx

	# Item's string name
	self.name = name

    def __repr__(self):
	return "Item(%s @ %d)" % (self.name, self.idx)

    # JSON friendly rep of our state
    def for_json(self):
	assert self.idx != -1
	return {"iid": self.iid, "name": self.name, "idx": self.idx}

    # Save ourselves to the DB
    def sync(self):
	db = self.parent.get_db()
	c = db.cursor()
	c.execute("insert or replace into items values(?,?,?,?) ",
	    (self.iid, self.parent.lid, self.name, self.idx))
	db.commit()
	c.close()
	db.close()

    def assign(self, idx):
	# No change
	if self.idx == idx:

Changes to js/shopr.js.

20
21
22
23
24
25
26



27
28
29
30
31
32
33
34
35
..
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
...
163
164
165
166
167
168
169


170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
...
208
209
210
211
212
213
214

215
216

217
218

219
220
221
222
223
224
225
//  item: {name: "item name", idx: <int>}
var cur_names = null, show_names = null;

// Set of indices currently being displayed
// Initial state, show all
const cur_idxs = new Set([0, 1, 2]);




//
// These goggle the filtering based on color
//
// Each toggles between darker version of color (showing it)
//  and its lighter counterpart.
//
function filtR() {
    if (cur_idxs.delete(0)) {
	spanR.style.background = "pink";
................................................................................
    }
    show_new_list(cur_names);
}
function filtY() {
    if (cur_idxs.delete(2)) {
	spanY.style.background = "#F3E5AB";
    } else {
	cur_idxs.add(0);
	spanY.style.background = "gold";
    }
    show_new_list(cur_names);
}

// Currently displayed page elem
var curdiv = null;
................................................................................
    // If change already pending, ignore them
    if (cur_bright != null) {
	return false;
    }

    // The "id" of the element encodes its row/col
    //  "item<row>,<col>"


    const tup = e.id.substring(4).split('_');
    const row = parseInt(tup[0]), col = parseInt(tup[1]);

    // We don't switch the item; we flag it as having a change
    //  on the way, and tell the server.  It'll wake up our XHR
    //  and let us update that way.
    set_bright(e);

    // So tell the server...
    const msg = {op: "idx", row: row, col: col};
    const req = new XMLHttpRequest();
    req.open("PUT", "/l/" + encodeURIComponent(cur_list) +
	".json" + authURL);
    req.send(JSON.stringify(msg));
}

// Tear down old list display, paint new one
................................................................................

	// Red/Green/Yellow tappables
	// The element "id" is item<row>_<col> so click
	//  handlers can quickly figure out which one
	//  was tapped.
	const s1 = document.createElement("span");
	s1.id = "item" + i.toString() + "_0";

	const s2 = document.createElement("span");
	s2.id = "item" + i.toString() + "_1";

	const s3 = document.createElement("span");
	s3.id = "item" + i.toString() + "_2";


	// The text goes in the one selected
	if (l.idx == 0) {
	    s1.textContent = l.name;
	    s1.classList.add("isel");
	} else if (l.idx == 1) {
	    s2.textContent = l.name;







>
>
>

|







 







|







 







>
>









|







 







>


>


>







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
..
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
...
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
...
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
//  item: {name: "item name", idx: <int>}
var cur_names = null, show_names = null;

// Set of indices currently being displayed
// Initial state, show all
const cur_idxs = new Set([0, 1, 2]);

// Map from the Element ID to its Item ID in the DB
const iids = {};

//
// These toggle the filtering based on color
//
// Each toggles between darker version of color (showing it)
//  and its lighter counterpart.
//
function filtR() {
    if (cur_idxs.delete(0)) {
	spanR.style.background = "pink";
................................................................................
    }
    show_new_list(cur_names);
}
function filtY() {
    if (cur_idxs.delete(2)) {
	spanY.style.background = "#F3E5AB";
    } else {
	cur_idxs.add(2);
	spanY.style.background = "gold";
    }
    show_new_list(cur_names);
}

// Currently displayed page elem
var curdiv = null;
................................................................................
    // If change already pending, ignore them
    if (cur_bright != null) {
	return false;
    }

    // The "id" of the element encodes its row/col
    //  "item<row>,<col>"
    // It also keys to its Item ID ("iid")
    const iid = iids[e.id];
    const tup = e.id.substring(4).split('_');
    const row = parseInt(tup[0]), col = parseInt(tup[1]);

    // We don't switch the item; we flag it as having a change
    //  on the way, and tell the server.  It'll wake up our XHR
    //  and let us update that way.
    set_bright(e);

    // So tell the server...
    const msg = {op: "idx", iid: iid, col: col};
    const req = new XMLHttpRequest();
    req.open("PUT", "/l/" + encodeURIComponent(cur_list) +
	".json" + authURL);
    req.send(JSON.stringify(msg));
}

// Tear down old list display, paint new one
................................................................................

	// Red/Green/Yellow tappables
	// The element "id" is item<row>_<col> so click
	//  handlers can quickly figure out which one
	//  was tapped.
	const s1 = document.createElement("span");
	s1.id = "item" + i.toString() + "_0";
	iids[s1.id] = l.iid;
	const s2 = document.createElement("span");
	s2.id = "item" + i.toString() + "_1";
	iids[s2.id] = l.iid;
	const s3 = document.createElement("span");
	s3.id = "item" + i.toString() + "_2";
	iids[s3.id] = l.iid;

	// The text goes in the one selected
	if (l.idx == 0) {
	    s1.textContent = l.name;
	    s1.classList.add("isel");
	} else if (l.idx == 1) {
	    s2.textContent = l.name;

Changes to mkdb.sql.

15
16
17
18
19
20
21

22
23
24
25
26
    global integer,
    gen integer,
    unique(owner,name)
);

/* Any one item within a list */
create table items(

    list integer,
    name text,
    idx integer,
    unique(list,name)
);







>





15
16
17
18
19
20
21
22
23
24
25
26
27
    global integer,
    gen integer,
    unique(owner,name)
);

/* Any one item within a list */
create table items(
    iid integer primary key autoincrement,
    list integer,
    name text,
    idx integer,
    unique(list,name)
);

Changes to put.py.

36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
	    return True,self.send_error(404)

	# Decode op
	try:
	    d = json.loads(buf)
	    op = str(d["op"])
	    if op == "idx":
		row = int(d["row"])
		col = int(d["col"])
	    else:
		return True,self.send_error(400, "No such op")
	except:
	    return True,self.send_error(400, "Malformed JSON")

	# Update item, and churn the list
	if op == "idx":
	    if l.set_idx(row, col):
		return True,self.send_json({"ok": True})
	    return True,self.send_error(400, "Bad item")

	# "Can't Happen"
	return True,self.send_error(500)







|








|





36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
	    return True,self.send_error(404)

	# Decode op
	try:
	    d = json.loads(buf)
	    op = str(d["op"])
	    if op == "idx":
		iid = int(d["iid"])
		col = int(d["col"])
	    else:
		return True,self.send_error(400, "No such op")
	except:
	    return True,self.send_error(400, "Malformed JSON")

	# Update item, and churn the list
	if op == "idx":
	    if l.set_idx(iid, col):
		return True,self.send_json({"ok": True})
	    return True,self.send_error(400, "Bad item")

	# "Can't Happen"
	return True,self.send_error(500)