widgets: Remove tictactoe example widget.

Steve asked me to remove this, since the tictactoe game was always
intended as a proof of concept. Now that we have poll and todo
widgets, the sample code for tictactoe has much less value.

We replace the content and type in test_widgets.py to maintain
coverage.
This commit is contained in:
YashRE42
2020-10-31 16:23:08 +00:00
committed by Tim Abbott
parent 87cdd8433d
commit 967efc32d2
12 changed files with 14 additions and 222 deletions

View File

@@ -278,7 +278,6 @@
"subs": false,
"message_view_header": false,
"templates": false,
"tictactoe_widget": false,
"timerender": false,
"todo_widget": false,
"top_left_corner": false,

View File

@@ -10,7 +10,7 @@ these features:
- **/ping**
- **/day** (and /night, /light, /dark)
- **/poll** (and /tictactoe, /todo) (BETA)
- **/poll** (and /todo) (BETA)
- **zform-enabled messages** for the trivia_quiz bot (BETA)
The beta features are only turned on for chat.zulip.org as
@@ -106,11 +106,10 @@ launch widgets by sending one of the following messages:
- /poll
- /todo
- /tictactoe
The webapp client provides the "widget experience" by
default. Other clients just show raw messages like
"/poll" or "/ticactoe", and should be adding support
"/poll", and should be adding support
for widgets soon.
Our customers have long requested a poll/survey widget.
@@ -154,8 +153,7 @@ Most of the logic is in the client; things are fairly opaque
to the server at this point.
The "submessage" architecture is generic.
Our tictactoe widget and todo list widget use
the same architecture as "poll".
Our todo list widget uses the same architecture as "poll".
If a client joins Zulip after a message has accumulated
several submessage events, it will see all of those
@@ -363,16 +361,14 @@ server also sends this payload to all clients who are
recipients of the parent message.
When the message gets to the client, the codepath for **zform**
is actually quite similar to what happens with more
customized widgets like **poll** and **tictactoe**. (In
fact, **zform** is a sibling of **poll** and **tictactoe**, and **zform**
just has a somewhat more generic job to do.) In
`static/js/widgetize.js` you will see where this code
converges, with snippets like this:
is actually quite similar to what happens with a more
customized widget like **poll**. (In fact, **zform** is a
sibling of **poll** and **zform** just has a somewhat more
generic job to do.) In `static/js/widgetize.js` you will see
where this code converges, with snippets like this:
~~~ js
widgets.poll = poll_widget;
widgets.tictactoe = tictactoe_widget;
widgets.todo = todo_widget;
widgets.zform = zform;
~~~

View File

@@ -2,7 +2,6 @@
set_global("$", global.make_zjquery());
set_global("poll_widget", {});
set_global("tictactoe_widget", {});
set_global("todo_widget", {});
set_global("zform", {});
set_global("document", "document-stub");

View File

@@ -59,7 +59,6 @@ import "../topic_zoom";
import "../filter";
import "../poll_widget";
import "../todo_widget";
import "../tictactoe_widget";
import "../zform";
import "../widgetize";
import "../submessage";

View File

@@ -151,7 +151,6 @@ declare let submessage: any;
declare let subs: any;
declare let message_view_header: any;
declare let templates: any;
declare let tictactoe_widget: any;
declare let timerender: any;
declare let todo_widget: any;
declare let stream_topic_history: any;

View File

@@ -1,148 +0,0 @@
"use strict";
const render_widgets_tictactoe_widget = require("../templates/widgets/tictactoe_widget.hbs");
const people = require("./people");
class TicTacToeData {
me = people.my_current_user_id();
square_values = new Map();
num_filled = 0;
waiting = false;
game_over = false;
is_game_over() {
const lines = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[1, 4, 7],
[2, 5, 8],
[3, 6, 9],
[1, 5, 9],
[7, 5, 3],
];
const line_won = (line) => {
const token = this.square_values.get(line[0]);
if (!token) {
return false;
}
return (
this.square_values.get(line[1]) === token &&
this.square_values.get(line[2]) === token
);
};
const board = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const filled = (i) => this.square_values.get(i);
return lines.some(line_won) || board.every(filled);
}
get_widget_data() {
const square = (i) => ({
val: this.square_values.get(i),
idx: i,
disabled: this.waiting || this.square_values.get(i) || this.game_over,
});
const squares = [
[square(1), square(2), square(3)],
[square(4), square(5), square(6)],
[square(7), square(8), square(9)],
];
const token = this.num_filled % 2 === 0 ? "X" : "O";
let move_status = token + "'s turn";
if (this.game_over) {
move_status = "Game over!";
}
const widget_data = {
squares,
move_status,
};
return widget_data;
}
handle = {
square_click: {
outbound: (idx) => {
const event = {
type: "square_click",
idx,
num_filled: this.num_filled,
};
return event;
},
inbound: (sender_id, data) => {
const idx = data.idx;
if (data.num_filled !== this.num_filled) {
blueslip.info("out of sync", data.num_filled);
return;
}
const token = this.num_filled % 2 === 0 ? "X" : "O";
if (this.square_values.has(idx)) {
return;
}
this.waiting = sender_id === this.me;
this.square_values.set(idx, token);
this.num_filled += 1;
this.game_over = this.is_game_over();
},
},
};
handle_event(sender_id, data) {
const type = data.type;
if (this.handle[type]) {
this.handle[type].inbound(sender_id, data);
}
}
}
exports.activate = function (opts) {
const elem = opts.elem;
const callback = opts.callback;
const tictactoe_data = new TicTacToeData();
function render() {
const widget_data = tictactoe_data.get_widget_data();
const html = render_widgets_tictactoe_widget(widget_data);
elem.html(html);
elem.find("button.tictactoe-square").on("click", (e) => {
e.stopPropagation();
const str_idx = $(e.target).attr("data-idx");
const idx = Number.parseInt(str_idx, 10);
const data = tictactoe_data.handle.square_click.outbound(idx);
callback(data);
});
}
elem.handle_events = function (events) {
for (const event of events) {
tictactoe_data.handle_event(event.sender_id, event.data);
}
render();
};
render();
};
window.tictactoe_widget = exports;

View File

@@ -2,7 +2,6 @@
const widgets = new Map([
["poll", poll_widget],
["tictactoe", tictactoe_widget],
["todo", todo_widget],
["zform", zform],
]);

View File

@@ -247,36 +247,6 @@
}
}
/* CSS for message content widgets */
table.tictactoe {
width: 80px;
margin-left: 0;
}
td.tictactoe {
width: 15px;
border: none;
padding: 2px;
}
button.tictactoe-square {
background-color: hsl(156, 30%, 62%);
width: 40px;
height: 40px;
border: none;
border-radius: 3px;
font-size: 25px;
color: hsl(0, 0%, 100%);
}
button.tictactoe-square:hover {
background-color: hsl(155, 5%, 53%);
}
button.tictactoe-square:disabled {
background-color: hsl(156, 33%, 81%);
}
/* embedded link previews */
.message_inline_image_title {
font-weight: bold;

View File

@@ -1,19 +0,0 @@
<h3>Tic-tac-toe</h3>
<h4>{{ move_status }}</h4>
<table class="tictactoe">
{{#each squares}}
<tr>
{{#each this}}
<td class="tictactoe">
<div>
<button {{#if this.disabled}}disabled{{/if}} class="tictactoe-square" data-idx="{{ idx }}">
{{ this.val }}
</button>
</div>
</td>
{{/each}}
</tr>
{{/each}}
</table>

View File

@@ -141,7 +141,6 @@ EXEMPT_FILES = {
'static/js/subs.js',
'static/js/message_view_header.js',
'static/js/templates.js',
'static/js/tictactoe_widget.js',
'static/js/timerender.js',
'static/js/todo_widget.js',
'static/js/topic_list.js',

View File

@@ -6,7 +6,7 @@ from zerver.models import SubMessage
def get_widget_data(content: str) -> Tuple[Optional[str], Optional[str]]:
valid_widget_types = ['tictactoe', 'poll', 'todo']
valid_widget_types = ['poll', 'todo']
tokens = content.split(' ')
# tokens[0] will always exist

View File

@@ -101,7 +101,7 @@ class WidgetContentTestCase(ZulipTestCase):
self.assertEqual(get_widget_data(content=message), (None, None))
# Add a positive check for context
self.assertEqual(get_widget_data(content='/tictactoe'), ('tictactoe', None))
self.assertEqual(get_widget_data(content='/todo'), ('todo', None))
def test_explicit_widget_content(self) -> None:
# Users can send widget_content directly on messages
@@ -146,14 +146,13 @@ class WidgetContentTestCase(ZulipTestCase):
self.assertEqual(submessage.msg_type, 'widget')
self.assertEqual(orjson.loads(submessage.content), expected_submessage_content)
def test_tictactoe(self) -> None:
# The tictactoe widget is mostly useful as a code sample,
# and it also helps us get test coverage that could apply
def test_todo(self) -> None:
# This also helps us get test coverage that could apply
# to future widgets.
sender = self.example_user('cordelia')
stream_name = 'Verona'
content = '/tictactoe'
content = '/todo'
payload = dict(
type="stream",
@@ -169,7 +168,7 @@ class WidgetContentTestCase(ZulipTestCase):
self.assertEqual(message.content, content)
expected_submessage_content = dict(
widget_type="tictactoe",
widget_type="todo",
extra_data=None,
)