mirror of
https://github.com/zulip/zulip.git
synced 2025-11-01 12:33:40 +00:00
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:
@@ -278,7 +278,6 @@
|
||||
"subs": false,
|
||||
"message_view_header": false,
|
||||
"templates": false,
|
||||
"tictactoe_widget": false,
|
||||
"timerender": false,
|
||||
"todo_widget": false,
|
||||
"top_left_corner": false,
|
||||
|
||||
@@ -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;
|
||||
~~~
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -59,7 +59,6 @@ import "../topic_zoom";
|
||||
import "../filter";
|
||||
import "../poll_widget";
|
||||
import "../todo_widget";
|
||||
import "../tictactoe_widget";
|
||||
import "../zform";
|
||||
import "../widgetize";
|
||||
import "../submessage";
|
||||
|
||||
1
static/js/global.d.ts
vendored
1
static/js/global.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
const widgets = new Map([
|
||||
["poll", poll_widget],
|
||||
["tictactoe", tictactoe_widget],
|
||||
["todo", todo_widget],
|
||||
["zform", zform],
|
||||
]);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user