default_streams: Convert inline form to modal.

Fixes #20838.
This commit is contained in:
Ganesh Pawar
2022-04-01 22:52:41 +05:30
committed by Tim Abbott
parent e993140660
commit fff9b334ff
9 changed files with 229 additions and 93 deletions

View File

@@ -218,18 +218,30 @@ async function test_custom_realm_emoji(page: Page): Promise<void> {
await test_delete_emoji(page);
}
async function test_add_default_stream(
page: Page,
stream_name: string,
row: string,
): Promise<void> {
// It matches with all the stream names which has 'O' as a substring (Rome, Scotland, Verona
// etc). 'O' is used to make sure that it works even if there are multiple suggestions.
// Uppercase 'O' is used instead of the lowercase version to make sure that the suggestions
// are case insensitive.
await common.select_item_via_typeahead(page, ".create_default_stream", "O", stream_name);
await page.click(".default-stream-form #do_submit_stream");
async function test_add_default_stream(page: Page): Promise<void> {
const streams = ["Denmark", "Venice"];
for (let i = 0; i < 2; i += 1) {
await page.click(`#select_default_stream_${i}_widget`);
await page.waitForSelector(".dropdown-list-container .list-item", {
visible: true,
});
const stream_to_select = `.dropdown-list-container .list-item[data-name="${streams[i]}"]`;
await page.waitForSelector(stream_to_select, {visible: true});
await page.click(stream_to_select);
assert((await page.$(".dropdown-list-container")) === null);
}
await page.click("#add-default-stream-modal .dialog_submit_button");
await common.wait_for_micromodal_to_close(page);
let stream_id = await common.get_stream_id(page, "Denmark");
let row = `.default_stream_row[data-stream-id='${CSS.escape(stream_id.toString())}']`;
await page.waitForSelector(row, {visible: true});
stream_id = await common.get_stream_id(page, "Venice");
row = `.default_stream_row[data-stream-id='${CSS.escape(stream_id.toString())}']`;
await page.waitForSelector(row, {visible: true});
}
@@ -242,13 +254,14 @@ async function test_remove_default_stream(page: Page, row: string): Promise<void
async function test_default_streams(page: Page): Promise<void> {
await page.click("li[data-section='default-streams-list']");
await page.waitForSelector(".create_default_stream", {visible: true});
await page.click("#show-add-default-streams-modal");
await common.wait_for_micromodal_to_open(page);
const stream_name = "Scotland";
const stream_name = "Denmark";
const stream_id = await common.get_stream_id(page, stream_name);
const row = `.default_stream_row[data-stream-id='${CSS.escape(stream_id.toString())}']`;
await test_add_default_stream(page, stream_name, row);
await test_add_default_stream(page);
await test_remove_default_stream(page, row);
}

View File

@@ -156,7 +156,7 @@ function read_external_account_field_data($profile_field_form) {
return field_data;
}
function update_choice_delete_btn($container, display_flag) {
export function update_choice_delete_btn($container, display_flag) {
const no_of_choice_row = $container.find(".choice-row").length;
// Disable delete button if there only one choice row
@@ -170,7 +170,7 @@ function update_choice_delete_btn($container, display_flag) {
}
}
function get_value_for_new_option(container) {
export function get_value_for_new_option(container) {
const $choice_rows = $(container).find(".choice-row");
if ($choice_rows.length === 0) {
// Value for the first option is 0.

View File

@@ -1,20 +1,87 @@
import $ from "jquery";
import render_add_default_streams from "../templates/settings/add_default_streams.hbs";
import render_admin_default_streams_list from "../templates/settings/admin_default_streams_list.hbs";
import render_default_stream_choice from "../templates/settings/default_stream_choice.hbs";
import * as channel from "./channel";
import * as dialog_widget from "./dialog_widget";
import * as dropdown_widget from "./dropdown_widget";
import * as hash_util from "./hash_util";
import {$t_html} from "./i18n";
import * as keydown_util from "./keydown_util";
import * as ListWidget from "./list_widget";
import * as loading from "./loading";
import {page_params} from "./page_params";
import * as scroll_util from "./scroll_util";
import * as settings_profile_fields from "./settings_profile_fields";
import * as stream_data from "./stream_data";
import * as sub_store from "./sub_store";
import * as typeahead_helper from "./typeahead_helper";
import * as ui_report from "./ui_report";
function add_choice_row($widget) {
if ($widget.closest(".choice-row").next().hasClass("choice-row")) {
return;
}
const $choices_div = $("#default-stream-choices");
settings_profile_fields.update_choice_delete_btn($choices_div, true);
create_choice_row();
}
function get_chosen_default_streams() {
// Return the set of stream id's of streams chosen in the default stream modal.
return new Set(
$("#default-stream-choices .choice-row .stream_name")
.map((_i, elem) => $(elem).data("stream-id")?.toString())
.get(),
);
}
function create_choice_row() {
const $container = $("#default-stream-choices");
const value = settings_profile_fields.get_value_for_new_option("#default-stream-choices");
const stream_dropdown_widget_name = `select_default_stream_${value}_widget`;
const row = render_default_stream_choice({value, stream_dropdown_widget_name});
$container.append(row);
// List of non-default streams that are not yet selected.
function get_options() {
const chosen_default_streams = get_chosen_default_streams();
return stream_data
.get_non_default_stream_names()
.filter((e) => !chosen_default_streams.has(e.unique_id));
}
function item_click_callback(event, dropdown) {
const $selected_stream = $(event.currentTarget);
const selected_stream_name = $selected_stream.attr("data-name");
const selected_stream_id = Number.parseInt($selected_stream.data("unique-id"), 10);
const $stream_dropdown_widget = $(`#${CSS.escape(stream_dropdown_widget_name)}`);
const $stream_name = $stream_dropdown_widget.find(".stream_name");
$stream_name.text(selected_stream_name);
$stream_name.data("stream-id", selected_stream_id);
add_choice_row($stream_dropdown_widget);
dropdown.hide();
$("#add-default-stream-modal .dialog_submit_button").prop("disabled", false);
event.stopPropagation();
event.preventDefault();
}
dropdown_widget.setup(
{
target: `#${stream_dropdown_widget_name}`,
placement: "bottom-start",
},
get_options,
item_click_callback,
{
show_on_target_enter_keypress: true,
},
);
}
const meta = {
loaded: false,
};
@@ -71,23 +138,6 @@ export function update_default_streams_table() {
}
}
function make_stream_default(stream_id) {
const data = {
stream_id,
};
const $default_stream_status = $("#admin-default-stream-status");
$default_stream_status.hide();
channel.post({
url: "/json/default_streams",
data,
error(xhr) {
ui_report.error($t_html({defaultMessage: "Failed"}), xhr, $default_stream_status);
$default_stream_status.show();
},
});
}
export function delete_default_stream(stream_id, $default_stream_row, $alert_element) {
channel.del({
url: "/json/default_streams?" + $.param({stream_id}),
@@ -100,6 +150,79 @@ export function delete_default_stream(stream_id, $default_stream_row, $alert_ele
});
}
function delete_choice_row(e) {
const $row = $(e.currentTarget).parent();
const $container = $row.parent();
$row.remove();
settings_profile_fields.update_choice_delete_btn($container, false);
// Disable the submit button if no streams are selected.
$("#add-default-stream-modal .dialog_submit_button").prop(
"disabled",
$(".choice-row").length <= 1,
);
}
function show_add_default_streams_modal() {
const html_body = render_add_default_streams();
function add_default_streams(e) {
e.preventDefault();
e.stopPropagation();
// Keep track of the number of successful requests. Close the modal
// only if all the requests are successful.
let successful_requests = 0;
const chosen_streams = get_chosen_default_streams();
function make_default_stream_request(stream_id) {
const data = {stream_id};
channel.post({
url: "/json/default_streams",
data,
success() {
successful_requests = successful_requests + 1;
if (successful_requests === chosen_streams.size) {
dialog_widget.close_modal();
}
},
error(xhr) {
ui_report.error(
$t_html({defaultMessage: "Failed adding one or more streams."}),
xhr,
$("#dialog_error"),
);
dialog_widget.hide_dialog_spinner();
},
});
}
for (const chosen_stream of chosen_streams) {
make_default_stream_request(chosen_stream);
}
}
function default_stream_post_render() {
$("#add-default-stream-modal .dialog_submit_button").prop("disabled", true);
create_choice_row();
settings_profile_fields.update_choice_delete_btn($("#default-stream-choices"), false);
$("#default-stream-choices").on("click", "button.delete-choice", delete_choice_row);
}
dialog_widget.launch({
html_heading: $t_html({defaultMessage: "Add default streams"}),
html_body,
html_submit_button: $t_html({defaultMessage: "Add"}),
help_link: "/help/set-default-streams-for-new-users",
id: "add-default-stream-modal",
loading_spinner: true,
on_click: add_default_streams,
post_render: default_stream_post_render,
});
}
export function set_up() {
build_page();
maybe_disable_widgets();
@@ -110,34 +233,11 @@ export function build_page() {
update_default_streams_table();
$(".create_default_stream").on("keypress", (e) => {
if (keydown_util.is_enter_event(e)) {
e.preventDefault();
e.stopPropagation();
const $default_stream_input = $(".create_default_stream");
make_stream_default(stream_data.get_stream_id($default_stream_input.val()));
$default_stream_input[0].value = "";
}
});
$(".create_default_stream").typeahead({
items: 5,
fixed: true,
source() {
return stream_data.get_non_default_stream_names();
},
highlighter(item) {
return typeahead_helper.render_typeahead_item({primary: item});
},
});
$(".default-stream-form").on("click", "#do_submit_stream", (e) => {
$("#show-add-default-streams-modal").on("click", (e) => {
e.preventDefault();
e.stopPropagation();
const $default_stream_input = $(".create_default_stream");
make_stream_default(stream_data.get_stream_id($default_stream_input.val()));
// Clear value inside input box
$default_stream_input[0].value = "";
show_add_default_streams_modal();
});
$("body").on("click", ".default_stream_row .remove-default-stream", function (e) {

View File

@@ -317,7 +317,10 @@ export function delete_sub(stream_id) {
export function get_non_default_stream_names() {
let subs = [...stream_info.values()];
subs = subs.filter((sub) => !is_default_stream_id(sub.stream_id) && !sub.invite_only);
const names = subs.map((sub) => sub.name);
const names = subs.map((sub) => ({
name: sub.name,
unique_id: sub.stream_id.toString(),
}));
return names;
}

View File

@@ -186,10 +186,6 @@ h3,
box-sizing: border-box;
}
.side-padded-container {
padding: 0 20px;
}
.setting_notification_sound,
.play_notification_sound {
display: inline;
@@ -657,11 +653,36 @@ input[type="checkbox"] {
margin: 10px 0;
}
.add-new-default-stream-box {
& input[type="text"] {
padding: 6px;
#add-default-stream-modal {
.dropdown-widget-button {
width: 116px;
display: inline-flex;
}
.default_stream_choices_table {
margin-top: 2px;
}
.choice-row {
margin-bottom: 10px;
&:last-of-type {
.delete-choice {
display: none;
}
}
}
}
.add_default_streams_button_container {
float: right;
#show-add-default-streams-modal {
padding: 5px 0;
min-width: 100px;
margin-top: 11px;
margin-right: 6px;
}
margin-bottom: 15px;
}
#add-custom-emoji-modal {

View File

@@ -0,0 +1,3 @@
<table class="default_stream_choices_table new-style">
<tbody id="default-stream-choices"></tbody>
</table>

View File

@@ -0,0 +1,11 @@
<div class="choice-row" data-value="{{value}}">
<div id="{{stream_dropdown_widget_name}}" class="dropdown-widget-button" role="button" tabindex="0">
<span class="stream_name">
Select stream
</span>
<i class="fa fa-chevron-down"></i>
</div>
<button type="button" class="button rounded small delete-choice" title="{{t 'Delete' }}">
<i class="fa fa-trash-o" aria-hidden="true"></i>
</button>
</div>

View File

@@ -1,29 +1,14 @@
<div id="admin-default-streams-list" class="settings-section" data-name="default-streams-list">
<div class="side-padded-container">
<p>{{t "Configure the default streams new users are subscribed to when joining your organization." }}</p>
</div>
{{#if is_admin}}
<form class="default-stream-form">
<div class="add-new-default-stream-box grey-box">
<div class="wrapper">
<div class="alert" id="admin-default-stream-status"></div>
<div class="settings-section-title">{{t "Add new default stream" }}</div>
<div class="inline-block" id="default_stream_inputs">
<label for="default_stream_name">{{t "Stream name" }}</label>
<input class="create_default_stream settings_text_input" type="text" placeholder="{{t 'Stream name' }}" name="stream_name" autocomplete="off" />
</div>
<div class="inline-block">
<button type="submit" id="do_submit_stream" class="button rounded sea-green">{{t "Add stream" }}</button>
</div>
</div>
</div>
</form>
{{/if}}
<p>{{t "Configure the default streams new users are subscribed to when joining your organization." }}</p>
<div class="settings_panel_list_header">
<h3>{{t "Default streams"}}</h3>
<input type="text" class="search" placeholder="{{t 'Filter default streams' }}" aria-label="{{t 'Filter streams' }}"/>
<div class="add_default_streams_button_container">
{{#if is_admin}}
<button type="submit" id="show-add-default-streams-modal" class="button rounded sea-green">{{t "Add stream" }}</button>
{{/if}}
<input type="text" class="search" placeholder="{{t 'Filter default streams' }}" aria-label="{{t 'Filter streams' }}"/>
</div>
</div>
<div class="progressive-table-wrapper" data-simplebar>

View File

@@ -415,7 +415,7 @@ test("default_stream_names", () => {
stream_data.add_sub(general);
const names = stream_data.get_non_default_stream_names();
assert.deepEqual(names.sort(), ["public"]);
assert.deepEqual(names.sort(), [{name: "public", unique_id: "102"}]);
const default_stream_ids = stream_data.get_default_stream_ids();
assert.deepEqual(default_stream_ids.sort(), [announce.stream_id, general.stream_id]);