chore: add precommit hook for testing code style (#1448)

* chore: add precommit hook for testing code style

* chore: use prettier + eslint for js code
This commit is contained in:
Charles Kerr 2020-09-13 21:41:32 -05:00 committed by GitHub
parent a4dd67ae45
commit be219ddee0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 5368 additions and 4999 deletions

View File

@ -1,10 +0,0 @@
{
"indent_size": 4,
"indent_char": " ",
"indent_level": 0,
"indent_with_tabs": false,
"preserve_newlines": true,
"max_preserve_newlines": 2,
"end_with_newline": true,
"jslint_happy": true
}

View File

@ -61,23 +61,24 @@ endif()
set(TR_VCS_REVISION_FILE "${CMAKE_SOURCE_DIR}/REVISION")
if(IS_DIRECTORY ${CMAKE_SOURCE_DIR}/.git)
find_package(Git)
endif()
if(NOT "$ENV{JENKINS_URL}" STREQUAL "" AND NOT "$ENV{GIT_COMMIT}" STREQUAL "")
set(TR_VCS_REVISION "$ENV{GIT_COMMIT}")
elseif(NOT "$ENV{TEAMCITY_PROJECT_NAME}" STREQUAL "" AND NOT "$ENV{BUILD_VCS_NUMBER}" STREQUAL "")
set(TR_VCS_REVISION "$ENV{BUILD_VCS_NUMBER}")
elseif(IS_DIRECTORY ${CMAKE_SOURCE_DIR}/.git)
find_package(Git)
if(GIT_FOUND)
execute_process(
COMMAND
${GIT_EXECUTABLE} rev-list --max-count=1 HEAD
WORKING_DIRECTORY
${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE
TR_VCS_REVISION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
endif()
elseif(GIT_FOUND)
execute_process(
COMMAND
${GIT_EXECUTABLE} rev-list --max-count=1 HEAD
WORKING_DIRECTORY
${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE
TR_VCS_REVISION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
endif()
if("${TR_VCS_REVISION}" STREQUAL "" AND EXISTS "${TR_VCS_REVISION_FILE}")
@ -664,4 +665,32 @@ set(CPACK_SOURCE_IGNORE_FILES
\\\\.git
)
## Code Formatting
if(GIT_FOUND)
message(STATUS "Looking for uncrustify")
execute_process(COMMAND
"${GIT_EXECUTABLE}" rev-parse --show-toplevel
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE TR_GIT_ROOT
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(TR_GIT_ROOT)
configure_file(
"${CMAKE_SOURCE_DIR}/format/pre-commit"
"${TR_GIT_ROOT}/.git/hooks/pre-commit"
COPYONLY
)
add_custom_target(check-format
COMMAND "${CMAKE_SOURCE_DIR}/format/format.sh" --all --check
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
)
add_custom_target(format
COMMAND "${CMAKE_SOURCE_DIR}/format/format.sh" --all
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
)
endif(TR_GIT_ROOT)
unset(TR_GIT_ROOT)
endif(GIT_FOUND)
include(CPack)

View File

@ -1,79 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
[ -z "${1:-}" ] || cd "$1"
echo '=================='
echo '=== uncrustify ==='
echo '=================='
echo ''
find \
cli \
daemon \
gtk \
libtransmission \
utils \
\( -name '*.c' -o -name '*.h' \) \
! \( -name 'ConvertUTF.*' -o -name 'jsonsl.*' -o -name 'wildmat.c' \) \
-print0 |
xargs \
-0 \
uncrustify \
--replace \
--no-backup \
-c uncrustify.cfg
find \
qt \
tests \
\( -name '*.cc' -o -name '*.h' \) \
-print0 |
xargs \
-0 \
uncrustify \
--replace \
--no-backup \
-l CPP \
-c uncrustify.cfg
echo ''
echo '================================================================='
echo '=== const placement (until uncrustify supports it, hopefully) ==='
echo '================================================================='
echo ''
find \
cli \
daemon \
gtk \
libtransmission \
qt \
utils \
\( -name '*.c' -o -name '*.cc' -o -name '*.h' \) \
! \( -name 'ConvertUTF.*' -o -name 'jsonsl.*' -o -name 'wildmat.c' \) \
-print0 |
xargs \
-0 \
-n1 \
perl \
-pi \
-e 'BEGIN { print STDOUT "Processing: ${ARGV[0]}\n" } s/((?:^|[(,;]|\bstatic\s+)\s*)\b(const)\b(?!\s+\w+\s*\[)/\1>\2</g'
echo ''
echo '==================='
echo '=== js-beautify ==='
echo '==================='
echo ''
find \
web \
! -path '*/jquery/*' \
-name '*.js' \
-print0 |
xargs \
-0 \
js-beautify \
--config .jsbeautifyrc \
--replace
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
exec "${DIR}/format/format.sh" --all

View File

@ -6,4 +6,5 @@ services:
volumes:
- .:/src
- ./code_style.sh:/code_style.sh:ro
- ./format:/format:ro
command: ["/bin/sh", "/code_style.sh", "/src"]

View File

@ -1,8 +1,11 @@
FROM alpine
RUN apk add --no-cache \
bash \
git \
npm \
perl \
uncrustify \
&& npm install -g \
js-beautify
eslint \
prettier

24
format/eslint.config.json Normal file
View File

@ -0,0 +1,24 @@
{
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"jquery": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 6,
"ecmaFeatures": {
"jsx": true
},
"sourceType": "module"
},
"rules": {
"curly": ["error", "all"],
"no-undef": "off",
"no-unused-vars": "off",
"no-var": "off",
"prefer-const": "off",
"semi": ["error", "always"]
}
}

140
format/format.sh Executable file
View File

@ -0,0 +1,140 @@
#!/usr/bin/env bash
find_sourcefiles_in_dirs(){
if [ "$changed" -ne "0" ]; then
files="$(git diff --name-only -- "${@}")";
elif [ "$staged" -ne "0" ]; then
files="$(git diff --name-only --staged -- "${@}")";
else
files="$(find "${@}")";
fi
# remove skipfiles
files="$(echo "${files}" | sort | comm -23 - "${root}/format/skipfiles.txt")"
echo "${files}"
}
# globals
all=0
changed=0
check=0
exitcode=0
staged=0
root="$(git rev-parse --show-toplevel)"
eslint_args=(-c "${root}/format/eslint.config.json")
prettier_args=(--config "${root}/format/prettier.config.json" --loglevel warn)
uncrustify_args=(-c "${root}/format/uncrustify.cfg")
# parse command line
for var in "$@"
do
case "$var" in
--changed) changed=1;;
--cached|--staged) staged=1;;
--check|--test) check=1;;
--all) all=1;;
esac
done
if [ "${changed}${staged}${all}" -eq "000" ]; then
echo "usage: $0 {--all|--changed|--staged} [--check]"
exit 1
fi
if [ "${check}" -ne "0" ]; then
prettier_args+=(--check);
uncrustify_args+=(--check -q);
else
eslint_args+=(--fix)
prettier_args+=(--write)
uncrustify_args+=(--replace --no-backup)
fi
cd "${root}" || exit 1
# format C/C++ files
tool='uncrustify'
tool_args=("${uncrustify_args[@]}")
cish_files=()
if ! command -v "${tool}" &> /dev/null; then
echo "skipping $tool (not found)"
else
# C
dirs=(cli daemon gtk libtransmission utils)
filestr=$(find_sourcefiles_in_dirs "${dirs[@]}") # newline-delimited string
filestr=$(echo "$filestr" | grep -e "\.[ch]$") # remove non-C files
IFS=$'\n' read -d '' -ra files <<< "${filestr}"; # convert to array
if [ ${#files[@]} -ne 0 ]; then
"${tool}" "${tool_args[@]}" -l C "${files[@]}" 1>/dev/null || exitcode=1
cish_files+=("${files[@]}")
fi
# C++
dirs=(qt tests)
filestr=$(find_sourcefiles_in_dirs "${dirs[@]}") # newline-delimited string
filestr=$(echo "$filestr" | grep -e "\.cc$" -e "\.h$") # remove non-C++ files
IFS=$'\n' read -d '' -ra files <<< "${filestr}"; # convert to array
if [ ${#files[@]} -ne 0 ]; then
"${tool}" "${tool_args[@]}" -l CPP "${files[@]}" 1>/dev/null || exitcode=1
cish_files+=("${files[@]}")
fi
fi
# check const placement.
# do this manually since neither clang-format nor uncrustify do it
if [ ${#cish_files[@]} -ne 0 ]; then
if [ "${check}" -ne "0" ]; then
matches="$(grep --line-number --with-filename -P '((?:^|[(,;]|\bstatic\s+)\s*)\b(const)\b(?!\s+\w+\s*\[)' "${cish_files[@]}")"
if [ -n "${matches}" ]; then
echo 'const in wrong place:'
echo "${matches}"
exitcode=1
fi
else
perl -pi -e 's/((?:^|[(,;]|\bstatic\s+)\s*)\b(const)\b(?!\s+\w+\s*\[)/\1>\2</g' "${cish_files[@]}"
fi
fi
if [ "${check}" -ne "0" ]; then
if [ "${exitcode}" -eq "1" ]; then
echo "style check failed. re-run format/format.sh without --check to reformat."
fi
fi
# format JS files
tool='prettier'
tool_args=("${prettier_args[@]}")
if ! command -v "${tool}" &> /dev/null; then
echo "skipping $tool (not found)"
else
dirs=(web)
filestr=$(find_sourcefiles_in_dirs "${dirs[@]}") # newline-delimited string
filestr=$(echo "$filestr" | grep -e "\.js$") # remove non-JS files
IFS=$'\n' read -d '' -ra files <<< "${filestr}"; # convert to array
if [ ${#files[@]} -ne 0 ]; then
"${tool}" "${tool_args[@]}" "${files[@]}" || exitcode=1
fi
fi
# lint JS files
tool='eslint'
tool_args=("${eslint_args[@]}")
if ! command -v "${tool}" &> /dev/null; then
echo "skipping $tool (not found)"
else
dirs=(web)
filestr=$(find_sourcefiles_in_dirs "${dirs[@]}") # newline-delimited string
filestr=$(echo "$filestr" | grep -e "\.js$") # remove non-JS files
IFS=$'\n' read -d '' -ra files <<< "${filestr}"; # convert to array
if [ ${#files[@]} -ne 0 ]; then
"${tool}" "${tool_args[@]}" "${files[@]}" || exitcode=1
fi
fi
exit $exitcode

6
format/pre-commit Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env sh
[ -z "$TR_SKIP_HOOKS" ] || exit 0
root="$(git rev-parse --show-toplevel)"
exec "${root}/format/format.sh" --staged --test

View File

@ -0,0 +1,19 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"printWidth": 100,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false,
"vueIndentScriptAndStyle": false
}

12
format/skipfiles.txt Normal file
View File

@ -0,0 +1,12 @@
libtransmission/ConvertUTF.c
libtransmission/ConvertUTF.h
libtransmission/jsonsl.c
libtransmission/jsonsl.h
libtransmission/wildmat.c
web/javascript/jquery/jquery-migrate.min.js
web/javascript/jquery/jquery.min.js
web/javascript/jquery/jquery.transmenu.js
web/javascript/jquery/jquery.transmenu.min.js
web/javascript/jquery/jquery.ui-contextmenu.js
web/javascript/jquery/jquery.ui-contextmenu.min.js
web/javascript/jquery/jquery-ui.min.js

View File

@ -1,4 +1,4 @@
# Uncrustify-0.70.1_f
# Uncrustify_d-0.71.0-13-42bfdca8
newlines = auto
input_tab_size = 8
output_tab_size = 8
@ -6,6 +6,7 @@ string_escape_char = 92
string_escape_char2 = 0
string_replace_tab_chars = false
tok_split_gte = false
disable_processing_nl_cont = false
disable_processing_cmt = " *INDENT-OFF*"
enable_processing_cmt = " *INDENT-ON*"
enable_digraphs = false
@ -159,6 +160,7 @@ sp_throw_paren = force
sp_after_throw = force
sp_catch_paren = force
sp_oc_catch_paren = ignore
sp_before_oc_proto_list = ignore
sp_oc_classname_paren = ignore
sp_version_paren = ignore
sp_scope_paren = ignore
@ -177,7 +179,7 @@ sp_finally_brace = ignore
sp_brace_finally = ignore
sp_try_brace = ignore
sp_getset_brace = ignore
sp_word_brace = remove
sp_word_brace_init_lst = ignore
sp_word_brace_ns = add
sp_before_dc = remove
sp_after_dc = remove
@ -284,6 +286,7 @@ indent_macro_brace = true
indent_member = 4
indent_member_single = true
indent_sing_line_comments = 0
indent_sparen_extra = 0
indent_relative_single_line_comments = false
indent_switch_case = 0
indent_switch_break_with_case = false
@ -308,7 +311,9 @@ indent_first_for_expr = false
indent_square_nl = false
indent_preserve_sql = false
indent_align_assign = false
indent_off_after_assign = false
indent_align_paren = false
indent_oc_inside_msg_sel = false
indent_oc_block = false
indent_oc_block_msg = 0
indent_oc_msg_colon = 0
@ -322,8 +327,11 @@ indent_min_vbrace_open = 0
indent_vbrace_open_on_tabstop = false
indent_token_after_brace = false
indent_cpp_lambda_body = true
indent_compound_literal_return = true
indent_using_block = true
indent_ternary_operator = 0
indent_inside_ternary_operator = false
indent_off_after_return = false
indent_off_after_return_new = false
indent_single_after_return = true
indent_ignore_asm_block = false
@ -367,6 +375,7 @@ nl_brace_else = force
nl_elseif_brace = force
nl_else_brace = force
nl_else_if = remove
nl_before_opening_brace_func_class_def = ignore
nl_before_if_closing_paren = remove
nl_brace_finally = ignore
nl_finally_brace = ignore
@ -433,6 +442,7 @@ nl_func_decl_start_multi_line = false
nl_func_def_start_multi_line = false
nl_func_decl_args = remove
nl_func_def_args = remove
nl_func_call_args = ignore
nl_func_decl_args_multi_line = false
nl_func_def_args_multi_line = false
nl_func_decl_end = remove
@ -445,9 +455,11 @@ nl_func_decl_empty = remove
nl_func_def_empty = remove
nl_func_call_empty = remove
nl_func_call_start = ignore
nl_func_call_end = ignore
nl_func_call_start_multi_line = false
nl_func_call_args_multi_line = false
nl_func_call_end_multi_line = false
nl_func_call_args_multi_line_ignore_closures = false
nl_template_start = false
nl_template_args = false
nl_template_end = false
@ -545,6 +557,10 @@ eat_blanks_before_close_brace = true
nl_remove_extra_newlines = 0
nl_after_annotation = ignore
nl_between_annotation = ignore
nl_before_whole_file_ifdef = 0
nl_after_whole_file_ifdef = 0
nl_before_whole_file_endif = 0
nl_after_whole_file_endif = 0
pos_arith = trail
pos_assign = trail
pos_bool = trail
@ -625,6 +641,7 @@ align_asm_colon = false
align_oc_msg_colon_span = 0
align_oc_msg_colon_first = false
align_oc_decl_colon = false
align_oc_msg_colon_xcode_like = false
cmt_width = 0
cmt_reflow_mode = 0
cmt_convert_tab_to_spaces = false
@ -673,6 +690,11 @@ mod_sort_case_sensitive = false
mod_sort_import = false
mod_sort_using = false
mod_sort_include = false
mod_sort_incl_import_prioritize_filename = false
mod_sort_incl_import_prioritize_extensionless = false
mod_sort_incl_import_prioritize_angle_over_quotes = false
mod_sort_incl_import_ignore_extension = false
mod_sort_incl_import_grouping_enabled = false
mod_move_case_break = false
mod_case_brace = ignore
mod_remove_empty_return = false
@ -708,9 +730,12 @@ use_indent_continue_only_once = true
indent_cpp_lambda_only_once = true
use_sp_after_angle_always = false
use_options_overriding_for_qt_macros = false
use_form_feed_no_more_as_whitespace_character = false
warn_level_tabs_found_in_verbatim_string_literals = 2
debug_max_number_of_loops = 0
debug_line_number_to_protocol = 0
type PtrArrayCompareFunc
type SIZE_T
type tr_session
# option(s) with 'not default' value: 226
# option(s) with 'not default' value: 225
#

View File

@ -43,7 +43,7 @@ extern char const* speed_T_str;
{ \
static GQuark q; \
\
if G_UNLIKELY(q == 0) \
if (G_UNLIKELY(q == 0)) \
{ \
q = g_quark_from_static_string(#QN); \
} \

View File

@ -267,7 +267,7 @@ TEST_F(VariantTest, bencParseAndReencode) {
{ " ", false }
};
for (const auto& test : tests)
for (auto const& test : tests)
{
tr_variant val;
char const* end = nullptr;

View File

@ -5,53 +5,53 @@
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
var transmission,
dialog,
isMobileDevice = RegExp("(iPhone|iPod|Android)").test(navigator.userAgent),
scroll_timeout;
let transmission,
dialog,
isMobileDevice = RegExp('(iPhone|iPod|Android)').test(navigator.userAgent),
scroll_timeout;
// http://forum.jquery.com/topic/combining-ui-dialog-and-tabs
$.fn.tabbedDialog = function (dialog_opts) {
this.tabs({
selected: 0
});
this.dialog(dialog_opts);
this.find('.ui-tab-dialog-close').append(this.parent().find('.ui-dialog-titlebar-close'));
this.find('.ui-tab-dialog-close').css({
'position': 'absolute',
'right': '0',
'top': '16px'
});
this.find('.ui-tab-dialog-close > a').css({
'float': 'none',
'padding': '0'
});
var tabul = this.find('ul:first');
this.parent().addClass('ui-tabs').prepend(tabul).draggable('option', 'handle', tabul);
this.siblings('.ui-dialog-titlebar').remove();
tabul.addClass('ui-dialog-titlebar');
this.tabs({
selected: 0,
});
this.dialog(dialog_opts);
this.find('.ui-tab-dialog-close').append(this.parent().find('.ui-dialog-titlebar-close'));
this.find('.ui-tab-dialog-close').css({
position: 'absolute',
right: '0',
top: '16px',
});
this.find('.ui-tab-dialog-close > a').css({
float: 'none',
padding: '0',
});
const tabul = this.find('ul:first');
this.parent().addClass('ui-tabs').prepend(tabul).draggable('option', 'handle', tabul);
this.siblings('.ui-dialog-titlebar').remove();
tabul.addClass('ui-dialog-titlebar');
};
/**
* Checks to see if the content actually changed before poking the DOM.
*/
function setInnerHTML(e, html) {
if (!e) {
return;
};
if (!e) {
return;
}
/* innerHTML is listed as a string, but the browser seems to change it.
* For example, "&infin;" gets changed to "∞" somewhere down the line.
* So, let's use an arbitrary different field to test our state... */
if (e.currentHTML != html) {
e.currentHTML = html;
e.innerHTML = html;
};
};
/* innerHTML is listed as a string, but the browser seems to change it.
* For example, "&infin;" gets changed to "∞" somewhere down the line.
* So, let's use an arbitrary different field to test our state... */
if (e.currentHTML != html) {
e.currentHTML = html;
e.innerHTML = html;
}
}
function sanitizeText(text) {
return text.replace(/</g, "&lt;").replace(/>/g, "&gt;");
};
return text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
/**
* Many of our text changes are triggered by periodic refreshes
@ -59,51 +59,51 @@ function sanitizeText(text) {
* so see if the text actually changed before poking the DOM.
*/
function setTextContent(e, text) {
if (e && (e.textContent != text)) {
e.textContent = text;
};
};
if (e && e.textContent != text) {
e.textContent = text;
}
}
/*
* Given a numerator and denominator, return a ratio string
*/
Math.ratio = function (numerator, denominator) {
var result = Math.floor(100 * numerator / denominator) / 100;
let result = Math.floor((100 * numerator) / denominator) / 100;
// check for special cases
if (result == Number.POSITIVE_INFINITY || result == Number.NEGATIVE_INFINITY) {
result = -2;
} else if (isNaN(result)) {
result = -1;
};
// check for special cases
if (result == Number.POSITIVE_INFINITY || result == Number.NEGATIVE_INFINITY) {
result = -2;
} else if (isNaN(result)) {
result = -1;
}
return result;
return result;
};
/**
* Round a string of a number to a specified number of decimal places
*/
Number.prototype.toTruncFixed = function (place) {
var ret = Math.floor(this * Math.pow(10, place)) / Math.pow(10, place);
return ret.toFixed(place);
const ret = Math.floor(this * Math.pow(10, place)) / Math.pow(10, place);
return ret.toFixed(place);
};
Number.prototype.toStringWithCommas = function () {
return this.toString().replace(/\B(?=(?:\d{3})+(?!\d))/g, ",");
return this.toString().replace(/\B(?=(?:\d{3})+(?!\d))/g, ',');
};
/*
* Trim whitespace from a string
*/
String.prototype.trim = function () {
return this.replace(/^\s*/, "").replace(/\s*$/, "");
return this.replace(/^\s*/, '').replace(/\s*$/, '');
};
/***
**** Preferences
***/
function Prefs() {};
function Prefs() {}
Prefs.prototype = {};
Prefs._RefreshRate = 'refresh_rate';
@ -133,25 +133,25 @@ Prefs._SortByState = 'state';
Prefs._CompactDisplayState = 'compact_display_state';
Prefs._Defaults = {
'filter': 'all',
'refresh_rate': 5,
'sort_direction': 'ascending',
'sort_method': 'name',
'turtle-state': false,
'compact_display_state': false
filter: 'all',
refresh_rate: 5,
sort_direction: 'ascending',
sort_method: 'name',
'turtle-state': false,
compact_display_state: false,
};
/*
* Set a preference option
*/
Prefs.setValue = function (key, val) {
if (!(key in Prefs._Defaults)) {
console.warn("unrecognized preference key '%s'", key);
};
if (!(key in Prefs._Defaults)) {
console.warn("unrecognized preference key '%s'", key);
}
var date = new Date();
date.setFullYear(date.getFullYear() + 1);
document.cookie = key + "=" + val + "; expires=" + date.toGMTString() + "; path=/";
const date = new Date();
date.setFullYear(date.getFullYear() + 1);
document.cookie = key + '=' + val + '; expires=' + date.toGMTString() + '; path=/';
};
/**
@ -161,30 +161,30 @@ Prefs.setValue = function (key, val) {
* @param fallback if the option isn't set, return this instead
*/
Prefs.getValue = function (key, fallback) {
var val;
let val;
if (!(key in Prefs._Defaults)) {
console.warn("unrecognized preference key '%s'", key);
};
if (!(key in Prefs._Defaults)) {
console.warn("unrecognized preference key '%s'", key);
}
var lines = document.cookie.split(';');
for (var i = 0, len = lines.length; !val && i < len; ++i) {
var line = lines[i].trim();
var delim = line.indexOf('=');
if ((delim === key.length) && line.indexOf(key) === 0) {
val = line.substring(delim + 1);
};
};
const lines = document.cookie.split(';');
for (let i = 0, len = lines.length; !val && i < len; ++i) {
const line = lines[i].trim();
const delim = line.indexOf('=');
if (delim === key.length && line.indexOf(key) === 0) {
val = line.substring(delim + 1);
}
}
// FIXME: we support strings and booleans... add number support too?
if (!val) {
val = fallback;
} else if (val === 'true') {
val = true;
} else if (val === 'false') {
val = false;
};
return val;
// FIXME: we support strings and booleans... add number support too?
if (!val) {
val = fallback;
} else if (val === 'true') {
val = true;
} else if (val === 'false') {
val = false;
}
return val;
};
/**
@ -193,75 +193,48 @@ Prefs.getValue = function (key, fallback) {
* @pararm o object to be populated (optional)
*/
Prefs.getClutchPrefs = function (o) {
if (!o) {
o = {};
};
for (var key in Prefs._Defaults) {
o[key] = Prefs.getValue(key, Prefs._Defaults[key]);
};
return o;
if (!o) {
o = {};
}
for (const key in Prefs._Defaults) {
o[key] = Prefs.getValue(key, Prefs._Defaults[key]);
}
return o;
};
// forceNumeric() plug-in implementation
jQuery.fn.forceNumeric = function () {
return this.each(function () {
$(this).keydown(function (e) {
var key = e.which || e.keyCode;
return !e.shiftKey && !e.altKey && !e.ctrlKey &&
// numbers
key >= 48 && key <= 57 ||
// Numeric keypad
key >= 96 && key <= 105 ||
// comma, period and minus, . on keypad
key === 190 || key === 188 || key === 109 || key === 110 ||
// Backspace and Tab and Enter
key === 8 || key === 9 || key === 13 ||
// Home and End
key === 35 || key === 36 ||
// left and right arrows
key === 37 || key === 39 ||
// Del and Ins
key === 46 || key === 45;
});
return this.each(function () {
$(this).keydown(function (e) {
const key = e.which || e.keyCode;
return (
(!e.shiftKey &&
!e.altKey &&
!e.ctrlKey &&
// numbers
key >= 48 &&
key <= 57) ||
// Numeric keypad
(key >= 96 && key <= 105) ||
// comma, period and minus, . on keypad
key === 190 ||
key === 188 ||
key === 109 ||
key === 110 ||
// Backspace and Tab and Enter
key === 8 ||
key === 9 ||
key === 13 ||
// Home and End
key === 35 ||
key === 36 ||
// left and right arrows
key === 37 ||
key === 39 ||
// Del and Ins
key === 46 ||
key === 45
);
});
};
/**
* http://blog.stevenlevithan.com/archives/parseuri
*
* parseUri 1.2.2
* (c) Steven Levithan <stevenlevithan.com>
* MIT License
*/
function parseUri(str) {
var o = parseUri.options;
var m = o.parser[o.strictMode ? "strict" : "loose"].exec(str);
var uri = {};
var i = 14;
while (i--) {
uri[o.key[i]] = m[i] || "";
};
uri[o.q.name] = {};
uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
if ($1) {
uri[o.q.name][$1] = $2;
};
});
return uri;
};
parseUri.options = {
strictMode: false,
key: ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "relative", "path", "directory", "file", "query", "anchor"],
q: {
name: "queryKey",
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
},
parser: {
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
}
});
};

View File

@ -6,113 +6,124 @@
*/
function Dialog() {
this.initialize();
};
this.initialize();
}
Dialog.prototype = {
/*
* Constructor
*/
initialize: function () {
/*
* Constructor
* Private Interface Variables
*/
initialize: function () {
this._container = $('#dialog_container');
this._heading = $('#dialog_heading');
this._message = $('#dialog_message');
this._cancel_button = $('#dialog_cancel_button');
this._confirm_button = $('#dialog_confirm_button');
this._callback = null;
/*
* Private Interface Variables
*/
this._container = $('#dialog_container');
this._heading = $('#dialog_heading');
this._message = $('#dialog_message');
this._cancel_button = $('#dialog_cancel_button');
this._confirm_button = $('#dialog_confirm_button');
this._callback = null;
// Observe the buttons
this._cancel_button.bind(
'click',
{
dialog: this,
},
this.onCancelClicked
);
this._confirm_button.bind(
'click',
{
dialog: this,
},
this.onConfirmClicked
);
},
// Observe the buttons
this._cancel_button.bind('click', {
dialog: this
}, this.onCancelClicked);
this._confirm_button.bind('click', {
dialog: this
}, this.onConfirmClicked);
},
/*--------------------------------------------
*
* E V E N T F U N C T I O N S
*
*--------------------------------------------*/
/*--------------------------------------------
*
* E V E N T F U N C T I O N S
*
*--------------------------------------------*/
executeCallback: function () {
this._callback();
dialog.hideDialog();
},
executeCallback: function () {
this._callback();
dialog.hideDialog();
},
hideDialog: function () {
$('body.dialog_showing').removeClass('dialog_showing');
this._container.hide();
transmission.hideMobileAddressbar();
transmission.updateButtonStates();
},
hideDialog: function () {
$('body.dialog_showing').removeClass('dialog_showing');
this._container.hide();
transmission.hideMobileAddressbar();
transmission.updateButtonStates();
},
isVisible: function () {
return this._container.is(':visible');
},
isVisible: function () {
return this._container.is(':visible');
},
onCancelClicked: function (event) {
event.data.dialog.hideDialog();
},
onCancelClicked: function (event) {
event.data.dialog.hideDialog();
},
onConfirmClicked: function (event) {
event.data.dialog.executeCallback();
},
onConfirmClicked: function (event) {
event.data.dialog.executeCallback();
},
/*--------------------------------------------
*
* I N T E R F A C E F U N C T I O N S
*
*--------------------------------------------*/
/*--------------------------------------------
*
* I N T E R F A C E F U N C T I O N S
*
*--------------------------------------------*/
/*
* Display a confirm dialog
*/
confirm: function (dialog_heading, dialog_message, confirm_button_label,
callback, cancel_button_label) {
if (!isMobileDevice) {
$('.dialog_container').hide();
};
setTextContent(this._heading[0], dialog_heading);
setTextContent(this._message[0], dialog_message);
setTextContent(this._cancel_button[0], cancel_button_label || 'Cancel');
setTextContent(this._confirm_button[0], confirm_button_label);
this._confirm_button.show();
this._callback = callback;
$('body').addClass('dialog_showing');
this._container.show();
transmission.updateButtonStates();
if (isMobileDevice) {
transmission.hideMobileAddressbar();
};
},
/*
* Display an alert dialog
*/
alert: function (dialog_heading, dialog_message, cancel_button_label) {
if (!isMobileDevice) {
$('.dialog_container').hide();
};
setTextContent(this._heading[0], dialog_heading);
setTextContent(this._message[0], dialog_message);
// jquery::hide() doesn't work here in Safari for some odd reason
this._confirm_button.css('display', 'none');
setTextContent(this._cancel_button[0], cancel_button_label);
// Just in case
$('#upload_container').hide();
$('#move_container').hide();
$('body').addClass('dialog_showing');
transmission.updateButtonStates();
if (isMobileDevice) {
transmission.hideMobileAddressbar();
};
this._container.show();
/*
* Display a confirm dialog
*/
confirm: function (
dialog_heading,
dialog_message,
confirm_button_label,
callback,
cancel_button_label
) {
if (!isMobileDevice) {
$('.dialog_container').hide();
}
setTextContent(this._heading[0], dialog_heading);
setTextContent(this._message[0], dialog_message);
setTextContent(this._cancel_button[0], cancel_button_label || 'Cancel');
setTextContent(this._confirm_button[0], confirm_button_label);
this._confirm_button.show();
this._callback = callback;
$('body').addClass('dialog_showing');
this._container.show();
transmission.updateButtonStates();
if (isMobileDevice) {
transmission.hideMobileAddressbar();
}
},
/*
* Display an alert dialog
*/
alert: function (dialog_heading, dialog_message, cancel_button_label) {
if (!isMobileDevice) {
$('.dialog_container').hide();
}
setTextContent(this._heading[0], dialog_heading);
setTextContent(this._message[0], dialog_message);
// jquery::hide() doesn't work here in Safari for some odd reason
this._confirm_button.css('display', 'none');
setTextContent(this._cancel_button[0], cancel_button_label);
// Just in case
$('#upload_container').hide();
$('#move_container').hide();
$('body').addClass('dialog_showing');
transmission.updateButtonStates();
if (isMobileDevice) {
transmission.hideMobileAddressbar();
}
this._container.show();
},
};

View File

@ -6,200 +6,207 @@
*/
function FileRow(torrent, depth, name, indices, even) {
var fields = {
have: 0,
indices: [],
isWanted: true,
priorityLow: false,
priorityNormal: false,
priorityHigh: false,
me: this,
size: 0,
torrent: null
};
const fields = {
have: 0,
indices: [],
isWanted: true,
priorityLow: false,
priorityNormal: false,
priorityHigh: false,
me: this,
size: 0,
torrent: null,
};
var elements = {
priority_low_button: null,
priority_normal_button: null,
priority_high_button: null,
progress: null,
root: null
};
const elements = {
priority_low_button: null,
priority_normal_button: null,
priority_high_button: null,
progress: null,
root: null,
};
var initialize = function (torrent, depth, name, indices, even) {
fields.torrent = torrent;
fields.indices = indices;
createRow(torrent, depth, name, even);
};
const initialize = function (torrent, depth, name, indices, even) {
fields.torrent = torrent;
fields.indices = indices;
createRow(torrent, depth, name, even);
};
var refreshWantedHTML = function () {
var e = $(elements.root);
e.toggleClass('skip', !fields.isWanted);
e.toggleClass('complete', isDone());
$(e[0].checkbox).prop('disabled', !isEditable());
$(e[0].checkbox).prop('checked', fields.isWanted);
};
const refreshWantedHTML = function () {
const e = $(elements.root);
e.toggleClass('skip', !fields.isWanted);
e.toggleClass('complete', isDone());
$(e[0].checkbox).prop('disabled', !isEditable());
$(e[0].checkbox).prop('checked', fields.isWanted);
};
var refreshProgressHTML = function () {
var pct = 100 * (fields.size ? (fields.have / fields.size) : 1.0)
var c = [Transmission.fmt.size(fields.have), ' of ', Transmission.fmt.size(fields.size), ' (', Transmission.fmt.percentString(pct), '%)'].join('');
setTextContent(elements.progress, c);
};
const refreshProgressHTML = function () {
const pct = 100 * (fields.size ? fields.have / fields.size : 1.0);
const c = [
Transmission.fmt.size(fields.have),
' of ',
Transmission.fmt.size(fields.size),
' (',
Transmission.fmt.percentString(pct),
'%)',
].join('');
setTextContent(elements.progress, c);
};
var refreshImpl = function () {
var i,
file,
have = 0,
size = 0,
wanted = false,
low = false,
normal = false,
high = false;
const refreshImpl = function () {
let i,
file,
have = 0,
size = 0,
wanted = false,
low = false,
normal = false,
high = false;
// loop through the file_indices that affect this row
for (i = 0; i < fields.indices.length; ++i) {
file = fields.torrent.getFile(fields.indices[i]);
have += file.bytesCompleted;
size += file.length;
wanted |= file.wanted;
switch (file.priority) {
case -1:
low = true;
break;
case 0:
normal = true;
break;
case 1:
high = true;
break;
}
}
// loop through the file_indices that affect this row
for (i = 0; i < fields.indices.length; ++i) {
file = fields.torrent.getFile(fields.indices[i]);
have += file.bytesCompleted;
size += file.length;
wanted |= file.wanted;
switch (file.priority) {
case -1:
low = true;
break;
case 0:
normal = true;
break;
case 1:
high = true;
break;
}
}
if ((fields.have != have) || (fields.size != size)) {
fields.have = have;
fields.size = size;
refreshProgressHTML();
}
if (fields.have != have || fields.size != size) {
fields.have = have;
fields.size = size;
refreshProgressHTML();
}
if (fields.isWanted !== wanted) {
fields.isWanted = wanted;
refreshWantedHTML();
}
if (fields.isWanted !== wanted) {
fields.isWanted = wanted;
refreshWantedHTML();
}
if (fields.priorityLow !== low) {
fields.priorityLow = low;
$(elements.priority_low_button).toggleClass('selected', low);
}
if (fields.priorityLow !== low) {
fields.priorityLow = low;
$(elements.priority_low_button).toggleClass('selected', low);
}
if (fields.priorityNormal !== normal) {
fields.priorityNormal = normal;
$(elements.priority_normal_button).toggleClass('selected', normal);
}
if (fields.priorityNormal !== normal) {
fields.priorityNormal = normal;
$(elements.priority_normal_button).toggleClass('selected', normal);
}
if (fields.priorityHigh !== high) {
fields.priorityHigh = high;
$(elements.priority_high_button).toggleClass('selected', high);
}
};
if (fields.priorityHigh !== high) {
fields.priorityHigh = high;
$(elements.priority_high_button).toggleClass('selected', high);
}
};
var isDone = function () {
return fields.have >= fields.size;
};
var isDone = function () {
return fields.have >= fields.size;
};
var isEditable = function () {
return (fields.torrent.getFileCount() > 1) && !isDone();
};
var isEditable = function () {
return fields.torrent.getFileCount() > 1 && !isDone();
};
var createRow = function (torrent, depth, name, even) {
var e, root, box;
var createRow = function (torrent, depth, name, even) {
let e, root, box;
root = document.createElement('li');
root.className = 'inspector_torrent_file_list_entry' + (even ? 'even' : 'odd');
elements.root = root;
root = document.createElement('li');
root.className = 'inspector_torrent_file_list_entry' + (even ? 'even' : 'odd');
elements.root = root;
e = document.createElement('input');
e.type = 'checkbox';
e.className = "file_wanted_control";
e.title = 'Download file';
$(e).change(function (ev) {
fireWantedChanged($(ev.currentTarget).prop('checked'));
});
root.checkbox = e;
root.appendChild(e);
e = document.createElement('input');
e.type = 'checkbox';
e.className = 'file_wanted_control';
e.title = 'Download file';
$(e).change(function (ev) {
fireWantedChanged($(ev.currentTarget).prop('checked'));
});
root.checkbox = e;
root.appendChild(e);
e = document.createElement('div');
e.className = 'file-priority-radiobox';
box = e;
e = document.createElement('div');
e.className = 'file-priority-radiobox';
box = e;
e = document.createElement('div');
e.className = 'low';
e.title = 'Low Priority';
$(e).click(function () {
firePriorityChanged(-1);
});
elements.priority_low_button = e;
box.appendChild(e);
e = document.createElement('div');
e.className = 'low';
e.title = 'Low Priority';
$(e).click(function () {
firePriorityChanged(-1);
});
elements.priority_low_button = e;
box.appendChild(e);
e = document.createElement('div');
e.className = 'normal';
e.title = 'Normal Priority';
$(e).click(function () {
firePriorityChanged(0);
});
elements.priority_normal_button = e;
box.appendChild(e);
e = document.createElement('div');
e.className = 'normal';
e.title = 'Normal Priority';
$(e).click(function () {
firePriorityChanged(0);
});
elements.priority_normal_button = e;
box.appendChild(e);
e = document.createElement('div');
e.title = 'High Priority';
e.className = 'high';
$(e).click(function () {
firePriorityChanged(1);
});
elements.priority_high_button = e;
box.appendChild(e);
e = document.createElement('div');
e.title = 'High Priority';
e.className = 'high';
$(e).click(function () {
firePriorityChanged(1);
});
elements.priority_high_button = e;
box.appendChild(e);
root.appendChild(box);
root.appendChild(box);
e = document.createElement('div');
e.className = "inspector_torrent_file_list_entry_name";
setTextContent(e, name);
$(e).click(fireNameClicked);
root.appendChild(e);
e = document.createElement('div');
e.className = 'inspector_torrent_file_list_entry_name';
setTextContent(e, name);
$(e).click(fireNameClicked);
root.appendChild(e);
e = document.createElement('div');
e.className = "inspector_torrent_file_list_entry_progress";
root.appendChild(e);
$(e).click(fireNameClicked);
elements.progress = e;
e = document.createElement('div');
e.className = 'inspector_torrent_file_list_entry_progress';
root.appendChild(e);
$(e).click(fireNameClicked);
elements.progress = e;
$(root).css('margin-left', '' + (depth * 16) + 'px');
$(root).css('margin-left', '' + depth * 16 + 'px');
refreshImpl();
return root;
};
refreshImpl();
return root;
};
var fireWantedChanged = function (do_want) {
$(fields.me).trigger('wantedToggled', [fields.indices, do_want]);
};
var fireWantedChanged = function (do_want) {
$(fields.me).trigger('wantedToggled', [fields.indices, do_want]);
};
var firePriorityChanged = function (priority) {
$(fields.me).trigger('priorityToggled', [fields.indices, priority]);
};
var firePriorityChanged = function (priority) {
$(fields.me).trigger('priorityToggled', [fields.indices, priority]);
};
var fireNameClicked = function () {
$(fields.me).trigger('nameClicked', [fields.me, fields.indices]);
};
var fireNameClicked = function () {
$(fields.me).trigger('nameClicked', [fields.me, fields.indices]);
};
/***
**** PUBLIC
***/
/***
**** PUBLIC
***/
this.getElement = function () {
return elements.root;
};
this.refresh = function () {
refreshImpl();
};
this.getElement = function () {
return elements.root;
};
this.refresh = function () {
refreshImpl();
};
initialize(torrent, depth, name, indices, even);
};
initialize(torrent, depth, name, indices, even);
}

View File

@ -6,287 +6,296 @@
*/
Transmission.fmt = (function () {
var speed_K = 1000;
var speed_K_str = 'kB/s';
var speed_M_str = 'MB/s';
var speed_G_str = 'GB/s';
const speed_K = 1000;
const speed_K_str = 'kB/s';
const speed_M_str = 'MB/s';
const speed_G_str = 'GB/s';
var size_K = 1000;
var size_B_str = 'B';
var size_K_str = 'kB';
var size_M_str = 'MB';
var size_G_str = 'GB';
var size_T_str = 'TB';
const size_K = 1000;
const size_B_str = 'B';
const size_K_str = 'kB';
const size_M_str = 'MB';
const size_G_str = 'GB';
const size_T_str = 'TB';
var mem_K = 1024;
var mem_B_str = 'B';
var mem_K_str = 'KiB';
var mem_M_str = 'MiB';
var mem_G_str = 'GiB';
var mem_T_str = 'TiB';
const mem_K = 1024;
const mem_B_str = 'B';
const mem_K_str = 'KiB';
const mem_M_str = 'MiB';
const mem_G_str = 'GiB';
const mem_T_str = 'TiB';
return {
return {
/*
* Format a percentage to a string
*/
percentString: function (x) {
if (x < 10.0) {
return x.toTruncFixed(2);
} else if (x < 100.0) {
return x.toTruncFixed(1);
} else {
return x.toTruncFixed(0);
}
},
/*
* Format a percentage to a string
*/
percentString: function (x) {
if (x < 10.0) {
return x.toTruncFixed(2);
} else if (x < 100.0) {
return x.toTruncFixed(1);
} else {
return x.toTruncFixed(0);
}
},
/*
* Format a ratio to a string
*/
ratioString: function (x) {
if (x === -1) {
return 'None';
}
if (x === -2) {
return '&infin;';
}
return this.percentString(x);
},
/*
* Format a ratio to a string
*/
ratioString: function (x) {
if (x === -1) {
return "None";
}
if (x === -2) {
return '&infin;';
}
return this.percentString(x);
},
/**
* Formats the a memory size into a human-readable string
* @param {Number} bytes the filesize in bytes
* @return {String} human-readable string
*/
mem: function (bytes) {
if (bytes < mem_K) {
return [bytes, mem_B_str].join(' ');
}
/**
* Formats the a memory size into a human-readable string
* @param {Number} bytes the filesize in bytes
* @return {String} human-readable string
*/
mem: function (bytes) {
if (bytes < mem_K)
return [bytes, mem_B_str].join(' ');
let convertedSize;
let unit;
var convertedSize;
var unit;
if (bytes < Math.pow(mem_K, 2)) {
convertedSize = bytes / mem_K;
unit = mem_K_str;
} else if (bytes < Math.pow(mem_K, 3)) {
convertedSize = bytes / Math.pow(mem_K, 2);
unit = mem_M_str;
} else if (bytes < Math.pow(mem_K, 4)) {
convertedSize = bytes / Math.pow(mem_K, 3);
unit = mem_G_str;
} else {
convertedSize = bytes / Math.pow(mem_K, 4);
unit = mem_T_str;
}
if (bytes < Math.pow(mem_K, 2)) {
convertedSize = bytes / mem_K;
unit = mem_K_str;
} else if (bytes < Math.pow(mem_K, 3)) {
convertedSize = bytes / Math.pow(mem_K, 2);
unit = mem_M_str;
} else if (bytes < Math.pow(mem_K, 4)) {
convertedSize = bytes / Math.pow(mem_K, 3);
unit = mem_G_str;
} else {
convertedSize = bytes / Math.pow(mem_K, 4);
unit = mem_T_str;
}
// try to have at least 3 digits and at least 1 decimal
return convertedSize <= 9.995
? [convertedSize.toTruncFixed(2), unit].join(' ')
: [convertedSize.toTruncFixed(1), unit].join(' ');
},
// try to have at least 3 digits and at least 1 decimal
return convertedSize <= 9.995 ? [convertedSize.toTruncFixed(2), unit].join(' ') : [convertedSize.toTruncFixed(1), unit].join(' ');
},
/**
* Formats the a disk capacity or file size into a human-readable string
* @param {Number} bytes the filesize in bytes
* @return {String} human-readable string
*/
size: function (bytes) {
if (bytes < size_K) {
return [bytes, size_B_str].join(' ');
}
/**
* Formats the a disk capacity or file size into a human-readable string
* @param {Number} bytes the filesize in bytes
* @return {String} human-readable string
*/
size: function (bytes) {
if (bytes < size_K) {
return [bytes, size_B_str].join(' ');
}
let convertedSize;
let unit;
var convertedSize;
var unit;
if (bytes < Math.pow(size_K, 2)) {
convertedSize = bytes / size_K;
unit = size_K_str;
} else if (bytes < Math.pow(size_K, 3)) {
convertedSize = bytes / Math.pow(size_K, 2);
unit = size_M_str;
} else if (bytes < Math.pow(size_K, 4)) {
convertedSize = bytes / Math.pow(size_K, 3);
unit = size_G_str;
} else {
convertedSize = bytes / Math.pow(size_K, 4);
unit = size_T_str;
}
if (bytes < Math.pow(size_K, 2)) {
convertedSize = bytes / size_K;
unit = size_K_str;
} else if (bytes < Math.pow(size_K, 3)) {
convertedSize = bytes / Math.pow(size_K, 2);
unit = size_M_str;
} else if (bytes < Math.pow(size_K, 4)) {
convertedSize = bytes / Math.pow(size_K, 3);
unit = size_G_str;
} else {
convertedSize = bytes / Math.pow(size_K, 4);
unit = size_T_str;
}
// try to have at least 3 digits and at least 1 decimal
return convertedSize <= 9.995
? [convertedSize.toTruncFixed(2), unit].join(' ')
: [convertedSize.toTruncFixed(1), unit].join(' ');
},
// try to have at least 3 digits and at least 1 decimal
return convertedSize <= 9.995 ? [convertedSize.toTruncFixed(2), unit].join(' ') : [convertedSize.toTruncFixed(1), unit].join(' ');
},
speedBps: function (Bps) {
return this.speed(this.toKBps(Bps));
},
speedBps: function (Bps) {
return this.speed(this.toKBps(Bps));
},
toKBps: function (Bps) {
return Math.floor(Bps / speed_K);
},
toKBps: function (Bps) {
return Math.floor(Bps / speed_K);
},
speed: function (KBps) {
let speed = KBps;
speed: function (KBps) {
var speed = KBps;
if (speed <= 999.95) {
// 0 KBps to 999 K
return [speed.toTruncFixed(0), speed_K_str].join(' ');
}
if (speed <= 999.95) { // 0 KBps to 999 K
return [speed.toTruncFixed(0), speed_K_str].join(' ');
}
speed /= speed_K;
speed /= speed_K;
if (speed <= 99.995) {
// 1 M to 99.99 M
return [speed.toTruncFixed(2), speed_M_str].join(' ');
}
if (speed <= 999.95) {
// 100 M to 999.9 M
return [speed.toTruncFixed(1), speed_M_str].join(' ');
}
if (speed <= 99.995) { // 1 M to 99.99 M
return [speed.toTruncFixed(2), speed_M_str].join(' ');
}
if (speed <= 999.95) { // 100 M to 999.9 M
return [speed.toTruncFixed(1), speed_M_str].join(' ');
}
// insane speeds
speed /= speed_K;
return [speed.toTruncFixed(2), speed_G_str].join(' ');
},
// insane speeds
speed /= speed_K;
return [speed.toTruncFixed(2), speed_G_str].join(' ');
},
timeInterval: function (seconds) {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
seconds = Math.floor(seconds % 60);
const d = days + ' ' + (days > 1 ? 'days' : 'day');
const h = hours + ' ' + (hours > 1 ? 'hours' : 'hour');
const m = minutes + ' ' + (minutes > 1 ? 'minutes' : 'minute');
const s = seconds + ' ' + (seconds > 1 ? 'seconds' : 'second');
timeInterval: function (seconds) {
var days = Math.floor(seconds / 86400),
hours = Math.floor((seconds % 86400) / 3600),
minutes = Math.floor((seconds % 3600) / 60),
seconds = Math.floor(seconds % 60),
d = days + ' ' + (days > 1 ? 'days' : 'day'),
h = hours + ' ' + (hours > 1 ? 'hours' : 'hour'),
m = minutes + ' ' + (minutes > 1 ? 'minutes' : 'minute'),
s = seconds + ' ' + (seconds > 1 ? 'seconds' : 'second');
if (days) {
if (days >= 4 || !hours) {
return d;
}
return d + ', ' + h;
}
if (hours) {
if (hours >= 4 || !minutes) {
return h;
}
return h + ', ' + m;
}
if (minutes) {
if (minutes >= 4 || !seconds) {
return m;
}
return m + ', ' + s;
}
return s;
},
timestamp: function (seconds) {
if (!seconds) {
return 'N/A';
}
var myDate = new Date(seconds * 1000);
var now = new Date();
var date = "";
var time = "";
var sameYear = now.getFullYear() === myDate.getFullYear();
var sameMonth = now.getMonth() === myDate.getMonth();
var dateDiff = now.getDate() - myDate.getDate();
if (sameYear && sameMonth && Math.abs(dateDiff) <= 1) {
if (dateDiff === 0) {
date = "Today";
} else if (dateDiff === 1) {
date = "Yesterday";
} else {
date = "Tomorrow";
}
} else {
date = myDate.toDateString();
}
var hours = myDate.getHours();
var period = "AM";
if (hours > 12) {
hours = hours - 12;
period = "PM";
}
if (hours === 0) {
hours = 12;
}
if (hours < 10) {
hours = "0" + hours;
}
var minutes = myDate.getMinutes();
if (minutes < 10) {
minutes = "0" + minutes;
}
var seconds = myDate.getSeconds();
if (seconds < 10) {
seconds = "0" + seconds;
}
time = [hours, minutes, seconds].join(':');
return [date, time, period].join(' ');
},
ngettext: function (msgid, msgid_plural, n) {
// TODO(i18n): http://doc.qt.digia.com/4.6/i18n-plural-rules.html
return n === 1 ? msgid : msgid_plural;
},
countString: function (msgid, msgid_plural, n) {
return [n.toStringWithCommas(), this.ngettext(msgid, msgid_plural, n)].join(' ');
},
peerStatus: function (flagStr) {
var formattedFlags = [];
for (var i = 0, flag; flag = flagStr[i]; ++i) {
var explanation = null;
switch (flag) {
case "O":
explanation = "Optimistic unchoke";
break;
case "D":
explanation = "Downloading from this peer";
break;
case "d":
explanation = "We would download from this peer if they'd let us";
break;
case "U":
explanation = "Uploading to peer";
break;
case "u":
explanation = "We would upload to this peer if they'd ask";
break;
case "K":
explanation = "Peer has unchoked us, but we're not interested";
break;
case "?":
explanation = "We unchoked this peer, but they're not interested";
break;
case "E":
explanation = "Encrypted Connection";
break;
case "H":
explanation = "Peer was discovered through Distributed Hash Table (DHT)";
break;
case "X":
explanation = "Peer was discovered through Peer Exchange (PEX)";
break;
case "I":
explanation = "Peer is an incoming connection";
break;
case "T":
explanation = "Peer is connected via uTP";
break;
};
if (!explanation) {
formattedFlags.push(flag);
} else {
formattedFlags.push("<span title=\"" + flag + ': ' + explanation + "\">" + flag + "</span>");
};
};
return formattedFlags.join('');
if (days) {
if (days >= 4 || !hours) {
return d;
}
};
return d + ', ' + h;
}
if (hours) {
if (hours >= 4 || !minutes) {
return h;
}
return h + ', ' + m;
}
if (minutes) {
if (minutes >= 4 || !seconds) {
return m;
}
return m + ', ' + s;
}
return s;
},
timestamp: function (seconds) {
if (!seconds) {
return 'N/A';
}
const myDate = new Date(seconds * 1000);
const now = new Date();
let date = '';
let time = '';
const sameYear = now.getFullYear() === myDate.getFullYear();
const sameMonth = now.getMonth() === myDate.getMonth();
const dateDiff = now.getDate() - myDate.getDate();
if (sameYear && sameMonth && Math.abs(dateDiff) <= 1) {
if (dateDiff === 0) {
date = 'Today';
} else if (dateDiff === 1) {
date = 'Yesterday';
} else {
date = 'Tomorrow';
}
} else {
date = myDate.toDateString();
}
let hours = myDate.getHours();
let period = 'AM';
if (hours > 12) {
hours = hours - 12;
period = 'PM';
}
if (hours === 0) {
hours = 12;
}
if (hours < 10) {
hours = '0' + hours;
}
let minutes = myDate.getMinutes();
if (minutes < 10) {
minutes = '0' + minutes;
}
seconds = myDate.getSeconds();
if (seconds < 10) {
seconds = '0' + seconds;
}
time = [hours, minutes, seconds].join(':');
return [date, time, period].join(' ');
},
ngettext: function (msgid, msgid_plural, n) {
// TODO(i18n): http://doc.qt.digia.com/4.6/i18n-plural-rules.html
return n === 1 ? msgid : msgid_plural;
},
countString: function (msgid, msgid_plural, n) {
return [n.toStringWithCommas(), this.ngettext(msgid, msgid_plural, n)].join(' ');
},
peerStatus: function (flagStr) {
const formattedFlags = [];
for (var i = 0, flag; (flag = flagStr[i]); ++i) {
let explanation = null;
switch (flag) {
case 'O':
explanation = 'Optimistic unchoke';
break;
case 'D':
explanation = 'Downloading from this peer';
break;
case 'd':
explanation = "We would download from this peer if they'd let us";
break;
case 'U':
explanation = 'Uploading to peer';
break;
case 'u':
explanation = "We would upload to this peer if they'd ask";
break;
case 'K':
explanation = "Peer has unchoked us, but we're not interested";
break;
case '?':
explanation = "We unchoked this peer, but they're not interested";
break;
case 'E':
explanation = 'Encrypted Connection';
break;
case 'H':
explanation = 'Peer was discovered through Distributed Hash Table (DHT)';
break;
case 'X':
explanation = 'Peer was discovered through Peer Exchange (PEX)';
break;
case 'I':
explanation = 'Peer is an incoming connection';
break;
case 'T':
explanation = 'Peer is connected via uTP';
break;
}
if (!explanation) {
formattedFlags.push(flag);
} else {
formattedFlags.push(
'<span title="' + flag + ': ' + explanation + '">' + flag + '</span>'
);
}
}
return formattedFlags.join('');
},
};
})();

File diff suppressed because it is too large Load Diff

View File

@ -6,46 +6,48 @@
*/
function main() {
// IE specific fixes here
if (jQuery.browser.msie) {
try {
document.execCommand("BackgroundImageCache", false, true);
} catch (err) {};
// IE specific fixes here
if (jQuery.browser.msie) {
try {
document.execCommand('BackgroundImageCache', false, true);
} catch (err) {
// no-op
}
}
if (jQuery.browser.safari) {
// Move search field's margin down for the styled input
document.getElementById('torrent_search').style['margin-top'] = 3;
}
if (isMobileDevice) {
window.onload = function () {
setTimeout(function () {
window.scrollTo(0, 1);
}, 500);
};
if (jQuery.browser.safari) {
// Move search field's margin down for the styled input
document.getElementById("torrent_search").style["margin-top"] = 3;
window.onorientationchange = function () {
setTimeout(function () {
window.scrollTo(0, 1);
}, 100);
};
if (window.navigator.standalone) {
// Fix min height for isMobileDevice when run in full screen mode from home screen
// so the footer appears in the right place
document.getElementById('torrent_container').style['min-height'] = '338px';
}
} else {
// Fix for non-Safari-3 browsers: dark borders to replace shadows.
Array.from(document.getElementsByClassName('dialog_window')).forEach(function (e) {
e.style['border'] = '1px solid #777';
});
}
if (isMobileDevice) {
window.onload = function () {
setTimeout(function () {
window.scrollTo(0, 1);
}, 500);
};
window.onorientationchange = function () {
setTimeout(function () {
window.scrollTo(0, 1);
}, 100);
};
if (window.navigator.standalone) {
// Fix min height for isMobileDevice when run in full screen mode from home screen
// so the footer appears in the right place
document.getElementById("torrent_container").style["min-height"] = "338px";
};
} else {
// Fix for non-Safari-3 browsers: dark borders to replace shadows.
Array.from(document.getElementsByClassName("dialog_window")).forEach(function (e) {
e.style["border"] = "1px solid #777";
});
};
// Initialise the dialog controller
dialog = new Dialog();
// Initialise the dialog controller
dialog = new Dialog();
// Initialise the main Transmission controller
transmission = new Transmission();
}
// Initialise the main Transmission controller
transmission = new Transmission();
};
document.addEventListener("DOMContentLoaded", main);
document.addEventListener('DOMContentLoaded', main);

View File

@ -1,42 +1,46 @@
var Notifications = {};
const Notifications = {};
$(document).ready(function () {
if (!window.webkitNotifications) {
return;
};
if (!window.webkitNotifications) {
return;
}
var notificationsEnabled = (window.webkitNotifications.checkPermission() === 0)
var toggle = $('#toggle_notifications');
let notificationsEnabled = window.webkitNotifications.checkPermission() === 0;
const toggle = $('#toggle_notifications');
toggle.show();
updateMenuTitle();
$(transmission).bind('downloadComplete seedingComplete', function (event, torrent) {
if (notificationsEnabled) {
var title = (event.type == 'downloadComplete' ? 'Download' : 'Seeding') + ' complete',
content = torrent.getName(),
notification;
toggle.show();
updateMenuTitle();
$(transmission).bind('downloadComplete seedingComplete', function (event, torrent) {
if (notificationsEnabled) {
let title = (event.type == 'downloadComplete' ? 'Download' : 'Seeding') + ' complete',
content = torrent.getName(),
notification;
notification = window.webkitNotifications.createNotification('style/transmission/images/logo.png', title, content);
notification.show();
setTimeout(function () {
notification.cancel();
}, 5000);
};
});
notification = window.webkitNotifications.createNotification(
'style/transmission/images/logo.png',
title,
content
);
notification.show();
setTimeout(function () {
notification.cancel();
}, 5000);
}
});
function updateMenuTitle() {
toggle.html((notificationsEnabled ? 'Disable' : 'Enable') + ' Notifications');
};
function updateMenuTitle() {
toggle.html((notificationsEnabled ? 'Disable' : 'Enable') + ' Notifications');
}
Notifications.toggle = function () {
if (window.webkitNotifications.checkPermission() !== 0) {
window.webkitNotifications.requestPermission(function () {
notificationsEnabled = (window.webkitNotifications.checkPermission() === 0);
updateMenuTitle();
});
} else {
notificationsEnabled = !notificationsEnabled;
updateMenuTitle();
};
};
Notifications.toggle = function () {
if (window.webkitNotifications.checkPermission() !== 0) {
window.webkitNotifications.requestPermission(function () {
notificationsEnabled = window.webkitNotifications.checkPermission() === 0;
updateMenuTitle();
});
} else {
notificationsEnabled = !notificationsEnabled;
updateMenuTitle();
}
};
});

View File

@ -6,82 +6,82 @@
*/
if (!Array.from) {
Array.from = (function () {
var toStr = Object.prototype.toString;
var isCallable = function (fn) {
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
};
var toInteger = function (value) {
var number = Number(value);
if (isNaN(number)) {
return 0;
}
if (number === 0 || !isFinite(number)) {
return number;
}
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
var maxSafeInteger = Math.pow(2, 53) - 1;
var toLength = function (value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
Array.from = (function () {
const toStr = Object.prototype.toString;
const isCallable = function (fn) {
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
};
const toInteger = function (value) {
const number = Number(value);
if (isNaN(number)) {
return 0;
}
if (number === 0 || !isFinite(number)) {
return number;
}
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
const maxSafeInteger = Math.pow(2, 53) - 1;
const toLength = function (value) {
const len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
// The length property of the from method is 1.
return function from(arrayLike /*, mapFn, thisArg */ ) {
// 1. Let C be the this value.
var C = this;
// The length property of the from method is 1.
return function from(arrayLike /*, mapFn, thisArg */) {
// 1. Let C be the this value.
const C = this;
// 2. Let items be ToObject(arrayLike).
var items = Object(arrayLike);
// 2. Let items be ToObject(arrayLike).
const items = Object(arrayLike);
// 3. ReturnIfAbrupt(items).
if (arrayLike == null) {
throw new TypeError("Array.from requires an array-like object - not null or undefined");
}
// 3. ReturnIfAbrupt(items).
if (arrayLike == null) {
throw new TypeError('Array.from requires an array-like object - not null or undefined');
}
// 4. If mapfn is undefined, then let mapping be false.
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
var T;
if (typeof mapFn !== 'undefined') {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError('Array.from: when provided, the second argument must be a function');
}
// 4. If mapfn is undefined, then let mapping be false.
const mapFn = arguments.length > 1 ? arguments[1] : void undefined;
let T;
if (typeof mapFn !== 'undefined') {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError('Array.from: when provided, the second argument must be a function');
}
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 2) {
T = arguments[2];
}
}
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 2) {
T = arguments[2];
}
}
// 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
var len = toLength(items.length);
// 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
const len = toLength(items.length);
// 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
// 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
const A = isCallable(C) ? Object(new C(len)) : new Array(len);
// 16. Let k be 0.
var k = 0;
// 17. Repeat, while k < len… (also steps a - h)
var kValue;
while (k < len) {
kValue = items[k];
if (mapFn) {
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
k += 1;
}
// 18. Let putStatus be Put(A, "length", len, true).
A.length = len;
// 20. Return A.
return A;
};
}());
// 16. Let k be 0.
let k = 0;
// 17. Repeat, while k < len… (also steps a - h)
let kValue;
while (k < len) {
kValue = items[k];
if (mapFn) {
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
k += 1;
}
// 18. Let putStatus be Put(A, "length", len, true).
A.length = len;
// 20. Return A.
return A;
};
})();
}

View File

@ -6,297 +6,303 @@
*/
function PrefsDialog(remote) {
const data = {
dialog: null,
remote: null,
elements: {},
var data = {
dialog: null,
remote: null,
elements: {},
// all the RPC session keys that we have gui controls for
keys: [
'alt-speed-down',
'alt-speed-time-begin',
'alt-speed-time-day',
'alt-speed-time-enabled',
'alt-speed-time-end',
'alt-speed-up',
'blocklist-enabled',
'blocklist-size',
'blocklist-url',
'dht-enabled',
'download-dir',
'encryption',
'idle-seeding-limit',
'idle-seeding-limit-enabled',
'lpd-enabled',
'peer-limit-global',
'peer-limit-per-torrent',
'peer-port',
'peer-port-random-on-start',
'pex-enabled',
'port-forwarding-enabled',
'rename-partial-files',
'seedRatioLimit',
'seedRatioLimited',
'speed-limit-down',
'speed-limit-down-enabled',
'speed-limit-up',
'speed-limit-up-enabled',
'start-added-torrents',
'utp-enabled',
],
// all the RPC session keys that we have gui controls for
keys: [
'alt-speed-down',
'alt-speed-time-begin',
'alt-speed-time-day',
'alt-speed-time-enabled',
'alt-speed-time-end',
'alt-speed-up',
'blocklist-enabled',
'blocklist-size',
'blocklist-url',
'dht-enabled',
'download-dir',
'encryption',
'idle-seeding-limit',
'idle-seeding-limit-enabled',
'lpd-enabled',
'peer-limit-global',
'peer-limit-per-torrent',
'peer-port',
'peer-port-random-on-start',
'pex-enabled',
'port-forwarding-enabled',
'rename-partial-files',
'seedRatioLimit',
'seedRatioLimited',
'speed-limit-down',
'speed-limit-down-enabled',
'speed-limit-up',
'speed-limit-up-enabled',
'start-added-torrents',
'utp-enabled'
],
// map of keys that are enabled only if a 'parent' key is enabled
groups: {
'alt-speed-time-enabled': [
'alt-speed-time-begin',
'alt-speed-time-day',
'alt-speed-time-end',
],
'blocklist-enabled': ['blocklist-url', 'blocklist-update-button'],
'idle-seeding-limit-enabled': ['idle-seeding-limit'],
seedRatioLimited: ['seedRatioLimit'],
'speed-limit-down-enabled': ['speed-limit-down'],
'speed-limit-up-enabled': ['speed-limit-up'],
},
};
// map of keys that are enabled only if a 'parent' key is enabled
groups: {
'alt-speed-time-enabled': ['alt-speed-time-begin',
'alt-speed-time-day',
'alt-speed-time-end'
],
'blocklist-enabled': ['blocklist-url',
'blocklist-update-button'
],
'idle-seeding-limit-enabled': ['idle-seeding-limit'],
'seedRatioLimited': ['seedRatioLimit'],
'speed-limit-down-enabled': ['speed-limit-down'],
'speed-limit-up-enabled': ['speed-limit-up']
const initTimeDropDown = function (e) {
let i, hour, mins, value, content;
for (i = 0; i < 24 * 4; ++i) {
hour = parseInt(i / 4, 10);
mins = (i % 4) * 15;
value = i * 15;
content = hour + ':' + (mins || '00');
e.options[i] = new Option(content, value);
}
};
const onPortChecked = function (response) {
const is_open = response['arguments']['port-is-open'];
const text = 'Port is <b>' + (is_open ? 'Open' : 'Closed') + '</b>';
const e = data.elements.root.find('#port-label');
setInnerHTML(e[0], text);
};
const setGroupEnabled = function (parent_key, enabled) {
let i, key, keys, root;
if (parent_key in data.groups) {
root = data.elements.root;
keys = data.groups[parent_key];
for (i = 0; (key = keys[i]); ++i) {
root.find('#' + key).attr('disabled', !enabled);
}
}
};
const onBlocklistUpdateClicked = function () {
data.remote.updateBlocklist();
setBlocklistButtonEnabled(false);
};
var setBlocklistButtonEnabled = function (b) {
const e = data.elements.blocklist_button;
e.attr('disabled', !b);
e.val(b ? 'Update' : 'Updating...');
};
const getValue = function (e) {
let str;
switch (e[0].type) {
case 'checkbox':
case 'radio':
return e.prop('checked');
case 'text':
case 'url':
case 'email':
case 'number':
case 'search':
case 'select-one':
str = e.val();
if (parseInt(str, 10).toString() === str) {
return parseInt(str, 10);
}
};
var initTimeDropDown = function (e) {
var i, hour, mins, value, content;
for (i = 0; i < 24 * 4; ++i) {
hour = parseInt(i / 4, 10);
mins = ((i % 4) * 15);
value = i * 15;
content = hour + ':' + (mins || '00');
e.options[i] = new Option(content, value);
if (parseFloat(str).toString() === str) {
return parseFloat(str);
}
return str;
default:
return null;
}
};
/* this callback is for controls whose changes can be applied
immediately, like checkboxs, radioboxes, and selects */
const onControlChanged = function (ev) {
const o = {};
o[ev.target.id] = getValue($(ev.target));
data.remote.savePrefs(o);
};
/* these two callbacks are for controls whose changes can't be applied
immediately -- like a text entry field -- because it takes many
change events for the user to get to the desired result */
const onControlFocused = function (ev) {
data.oldValue = getValue($(ev.target));
};
const onControlBlurred = function (ev) {
const newValue = getValue($(ev.target));
if (newValue !== data.oldValue) {
const o = {};
o[ev.target.id] = newValue;
data.remote.savePrefs(o);
delete data.oldValue;
}
};
const getDefaultMobileOptions = function () {
return {
width: $(window).width(),
height: $(window).height(),
position: ['left', 'top'],
};
};
var onPortChecked = function (response) {
var is_open = response['arguments']['port-is-open'];
var text = 'Port is <b>' + (is_open ? 'Open' : 'Closed') + '</b>';
var e = data.elements.root.find('#port-label');
setInnerHTML(e[0], text);
};
const initialize = function (remote) {
let i, key, e, o;
var setGroupEnabled = function (parent_key, enabled) {
var i, key, keys, root;
data.remote = remote;
if (parent_key in data.groups) {
root = data.elements.root;
keys = data.groups[parent_key];
e = $('#prefs-dialog');
data.elements.root = e;
for (i = 0; key = keys[i]; ++i) {
root.find('#' + key).attr('disabled', !enabled);
};
initTimeDropDown(e.find('#alt-speed-time-begin')[0]);
initTimeDropDown(e.find('#alt-speed-time-end')[0]);
o = isMobileDevice
? getDefaultMobileOptions()
: {
width: 350,
height: 400,
};
};
o.autoOpen = false;
o.show = o.hide = 'fade';
o.close = onDialogClosed;
e.tabbedDialog(o);
var onBlocklistUpdateClicked = function () {
data.remote.updateBlocklist();
setBlocklistButtonEnabled(false);
};
e = e.find('#blocklist-update-button');
data.elements.blocklist_button = e;
e.click(onBlocklistUpdateClicked);
var setBlocklistButtonEnabled = function (b) {
var e = data.elements.blocklist_button;
e.attr('disabled', !b);
e.val(b ? 'Update' : 'Updating...');
};
var getValue = function (e) {
var str;
switch (e[0].type) {
// listen for user input
for (i = 0; (key = data.keys[i]); ++i) {
e = data.elements.root.find('#' + key);
switch (e[0].type) {
case 'checkbox':
case 'radio':
return e.prop('checked');
case 'select-one':
e.change(onControlChanged);
break;
case 'text':
case 'url':
case 'email':
case 'number':
case 'search':
case 'select-one':
str = e.val();
if (parseInt(str, 10).toString() === str) {
return parseInt(str, 10);
};
if (parseFloat(str).toString() === str) {
return parseFloat(str);
};
return str;
e.focus(onControlFocused);
e.blur(onControlBlurred);
break;
default:
return null;
break;
}
}
};
const getValues = function () {
let i,
key,
val,
o = {},
keys = data.keys,
root = data.elements.root;
for (i = 0; (key = keys[i]); ++i) {
val = getValue(root.find('#' + key));
if (val !== null) {
o[key] = val;
}
}
return o;
};
var onDialogClosed = function () {
transmission.hideMobileAddressbar();
$(data.dialog).trigger('closed', getValues());
};
/****
***** PUBLIC FUNCTIONS
****/
// update the dialog's controls
this.set = function (o) {
let e, i, key, val;
const keys = data.keys;
const root = data.elements.root;
setBlocklistButtonEnabled(true);
for (i = 0; (key = keys[i]); ++i) {
val = o[key];
e = root.find('#' + key);
if (key === 'blocklist-size') {
// special case -- regular text area
e.text('' + val.toStringWithCommas());
} else {
switch (e[0].type) {
case 'checkbox':
case 'radio':
e.prop('checked', val);
setGroupEnabled(key, val);
break;
case 'text':
case 'url':
case 'email':
case 'number':
case 'search':
// don't change the text if the user's editing it.
// it's very annoying when that happens!
if (e[0] !== document.activeElement) {
e.val(val);
}
break;
case 'select-one':
e.val(val);
break;
default:
break;
}
};
}
}
};
/* this callback is for controls whose changes can be applied
immediately, like checkboxs, radioboxes, and selects */
var onControlChanged = function (ev) {
var o = {};
o[ev.target.id] = getValue($(ev.target));
data.remote.savePrefs(o);
};
this.show = function () {
transmission.hideMobileAddressbar();
/* these two callbacks are for controls whose changes can't be applied
immediately -- like a text entry field -- because it takes many
change events for the user to get to the desired result */
var onControlFocused = function (ev) {
data.oldValue = getValue($(ev.target));
};
setBlocklistButtonEnabled(true);
data.remote.checkPort(onPortChecked, this);
data.elements.root.dialog('open');
};
var onControlBlurred = function (ev) {
var newValue = getValue($(ev.target));
if (newValue !== data.oldValue) {
var o = {};
o[ev.target.id] = newValue;
data.remote.savePrefs(o);
delete data.oldValue;
}
};
this.close = function () {
transmission.hideMobileAddressbar();
data.elements.root.dialog('close');
};
var getDefaultMobileOptions = function () {
return {
width: $(window).width(),
height: $(window).height(),
position: ['left', 'top']
};
};
this.shouldAddedTorrentsStart = function () {
return data.elements.root.find('#start-added-torrents')[0].checked;
};
var initialize = function (remote) {
var i, key, e, o;
data.remote = remote;
e = $('#prefs-dialog');
data.elements.root = e;
initTimeDropDown(e.find('#alt-speed-time-begin')[0]);
initTimeDropDown(e.find('#alt-speed-time-end')[0]);
o = isMobileDevice ? getDefaultMobileOptions() : {
width: 350,
height: 400
};
o.autoOpen = false;
o.show = o.hide = 'fade';
o.close = onDialogClosed;
e.tabbedDialog(o);
e = e.find('#blocklist-update-button');
data.elements.blocklist_button = e;
e.click(onBlocklistUpdateClicked);
// listen for user input
for (i = 0; key = data.keys[i]; ++i) {
e = data.elements.root.find('#' + key);
switch (e[0].type) {
case 'checkbox':
case 'radio':
case 'select-one':
e.change(onControlChanged);
break;
case 'text':
case 'url':
case 'email':
case 'number':
case 'search':
e.focus(onControlFocused);
e.blur(onControlBlurred);
default:
break;
};
};
};
var getValues = function () {
var i, key, val, o = {},
keys = data.keys,
root = data.elements.root;
for (i = 0; key = keys[i]; ++i) {
val = getValue(root.find('#' + key));
if (val !== null) {
o[key] = val;
};
};
return o;
};
var onDialogClosed = function () {
transmission.hideMobileAddressbar();
$(data.dialog).trigger('closed', getValues());
};
/****
***** PUBLIC FUNCTIONS
****/
// update the dialog's controls
this.set = function (o) {
var e, i, key, val;
var keys = data.keys;
var root = data.elements.root;
setBlocklistButtonEnabled(true);
for (i = 0; key = keys[i]; ++i) {
val = o[key];
e = root.find('#' + key);
if (key === 'blocklist-size') {
// special case -- regular text area
e.text('' + val.toStringWithCommas());
} else switch (e[0].type) {
case 'checkbox':
case 'radio':
e.prop('checked', val);
setGroupEnabled(key, val);
break;
case 'text':
case 'url':
case 'email':
case 'number':
case 'search':
// don't change the text if the user's editing it.
// it's very annoying when that happens!
if (e[0] !== document.activeElement) {
e.val(val);
};
break;
case 'select-one':
e.val(val);
break;
default:
break;
};
};
};
this.show = function () {
transmission.hideMobileAddressbar();
setBlocklistButtonEnabled(true);
data.remote.checkPort(onPortChecked, this);
data.elements.root.dialog('open');
};
this.close = function () {
transmission.hideMobileAddressbar();
data.elements.root.dialog('close');
};
this.shouldAddedTorrentsStart = function () {
return data.elements.root.find('#start-added-torrents')[0].checked;
};
data.dialog = this;
initialize(remote);
};
data.dialog = this;
initialize(remote);
}

View File

@ -5,282 +5,298 @@
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
var RPC = {
_DaemonVersion: 'version',
_DownSpeedLimit: 'speed-limit-down',
_DownSpeedLimited: 'speed-limit-down-enabled',
_QueueMoveTop: 'queue-move-top',
_QueueMoveBottom: 'queue-move-bottom',
_QueueMoveUp: 'queue-move-up',
_QueueMoveDown: 'queue-move-down',
_Root: '../rpc',
_TurtleDownSpeedLimit: 'alt-speed-down',
_TurtleState: 'alt-speed-enabled',
_TurtleUpSpeedLimit: 'alt-speed-up',
_UpSpeedLimit: 'speed-limit-up',
_UpSpeedLimited: 'speed-limit-up-enabled'
const RPC = {
_DaemonVersion: 'version',
_DownSpeedLimit: 'speed-limit-down',
_DownSpeedLimited: 'speed-limit-down-enabled',
_QueueMoveTop: 'queue-move-top',
_QueueMoveBottom: 'queue-move-bottom',
_QueueMoveUp: 'queue-move-up',
_QueueMoveDown: 'queue-move-down',
_Root: '../rpc',
_TurtleDownSpeedLimit: 'alt-speed-down',
_TurtleState: 'alt-speed-enabled',
_TurtleUpSpeedLimit: 'alt-speed-up',
_UpSpeedLimit: 'speed-limit-up',
_UpSpeedLimited: 'speed-limit-up-enabled',
};
function TransmissionRemote(controller) {
this.initialize(controller);
return this;
this.initialize(controller);
return this;
}
TransmissionRemote.prototype = {
/*
* Constructor
*/
initialize: function (controller) {
this._controller = controller;
this._error = '';
this._token = '';
},
/*
* Constructor
*/
initialize: function (controller) {
this._controller = controller;
this._error = '';
this._token = '';
},
/*
* Display an error if an ajax request fails, and stop sending requests
* or on a 409, globally set the X-Transmission-Session-Id and resend
*/
ajaxError: function (request, error_string, exception, ajaxObject) {
var token;
var remote = this;
/*
* Display an error if an ajax request fails, and stop sending requests
* or on a 409, globally set the X-Transmission-Session-Id and resend
*/
ajaxError: function (request, error_string, exception, ajaxObject) {
let token;
const remote = this;
// set the Transmission-Session-Id on a 409
if (request.status === 409 && (token = request.getResponseHeader('X-Transmission-Session-Id'))) {
remote._token = token;
$.ajax(ajaxObject);
return;
};
remote._error = request.responseText ? request.responseText.trim().replace(/(<([^>]+)>)/ig, "") : "";
if (!remote._error.length) {
remote._error = 'Server not responding';
};
dialog.confirm('Connection Failed',
'Could not connect to the server. You may need to reload the page to reconnect.',
'Details',
function () {
alert(remote._error);
},
'Dismiss');
remote._controller.togglePeriodicSessionRefresh(false);
},
appendSessionId: function (XHR) {
if (this._token) {
XHR.setRequestHeader('X-Transmission-Session-Id', this._token);
};
},
sendRequest: function (data, callback, context, async) {
var remote = this;
if (typeof (async) != 'boolean') {
async = true;
};
var ajaxSettings = {
url: RPC._Root,
type: 'POST',
contentType: 'json',
dataType: 'json',
cache: false,
data: JSON.stringify(data),
beforeSend: function (XHR) {
remote.appendSessionId(XHR);
},
error: function (request, error_string, exception) {
remote.ajaxError(request, error_string, exception, ajaxSettings);
},
success: callback,
context: context,
async: async
};
$.ajax(ajaxSettings);
},
loadDaemonPrefs: function (callback, context, async) {
var o = {
method: 'session-get'
};
this.sendRequest(o, callback, context, async);
},
checkPort: function (callback, context, async) {
var o = {
method: 'port-test'
};
this.sendRequest(o, callback, context, async);
},
renameTorrent: function (torrentIds, oldpath, newname, callback, context) {
var o = {
method: 'torrent-rename-path',
arguments: {
'ids': torrentIds,
'path': oldpath,
'name': newname
}
};
this.sendRequest(o, callback, context);
},
loadDaemonStats: function (callback, context, async) {
var o = {
method: 'session-stats'
};
this.sendRequest(o, callback, context, async);
},
updateTorrents: function (torrentIds, fields, callback, context) {
var o = {
method: 'torrent-get',
arguments: {
'fields': fields
}
};
if (torrentIds) {
o['arguments'].ids = torrentIds;
};
this.sendRequest(o, function (response) {
var args = response['arguments'];
callback.call(context, args.torrents, args.removed);
});
},
getFreeSpace: function (dir, callback, context) {
var o = {
method: 'free-space',
arguments: {
path: dir
}
};
this.sendRequest(o, function (response) {
var args = response['arguments'];
callback.call(context, args.path, args['size-bytes']);
});
},
changeFileCommand: function (torrentId, fileIndices, command) {
var remote = this,
args = {
ids: [torrentId]
};
args[command] = fileIndices;
this.sendRequest({
arguments: args,
method: 'torrent-set'
}, function () {
remote._controller.refreshTorrents([torrentId]);
});
},
sendTorrentSetRequests: function (method, torrent_ids, args, callback, context) {
if (!args) {
args = {};
};
args['ids'] = torrent_ids;
var o = {
method: method,
arguments: args
};
this.sendRequest(o, callback, context);
},
sendTorrentActionRequests: function (method, torrent_ids, callback, context) {
this.sendTorrentSetRequests(method, torrent_ids, null, callback, context);
},
startTorrents: function (torrent_ids, noqueue, callback, context) {
var name = noqueue ? 'torrent-start-now' : 'torrent-start';
this.sendTorrentActionRequests(name, torrent_ids, callback, context);
},
stopTorrents: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests('torrent-stop', torrent_ids, callback, context);
},
moveTorrents: function (torrent_ids, new_location, callback, context) {
this.sendTorrentSetRequests('torrent-set-location', torrent_ids, {
"move": true,
"location": new_location
}, callback, context);
},
removeTorrents: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests('torrent-remove', torrent_ids, callback, context);
},
removeTorrentsAndData: function (torrents) {
var remote = this;
var o = {
method: 'torrent-remove',
arguments: {
'delete-local-data': true,
ids: []
}
};
if (torrents) {
for (var i = 0, len = torrents.length; i < len; ++i) {
o.arguments.ids.push(torrents[i].getId());
};
};
this.sendRequest(o, function () {
remote._controller.refreshTorrents();
});
},
verifyTorrents: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests('torrent-verify', torrent_ids, callback, context);
},
reannounceTorrents: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests('torrent-reannounce', torrent_ids, callback, context);
},
addTorrentByUrl: function (url, options) {
var remote = this;
if (url.match(/^[0-9a-f]{40}$/i)) {
url = 'magnet:?xt=urn:btih:' + url;
}
var o = {
method: 'torrent-add',
arguments: {
paused: (options.paused),
filename: url
}
};
this.sendRequest(o, function () {
remote._controller.refreshTorrents();
});
},
savePrefs: function (args) {
var remote = this;
var o = {
method: 'session-set',
arguments: args
};
this.sendRequest(o, function () {
remote._controller.loadDaemonPrefs();
});
},
updateBlocklist: function () {
var remote = this;
var o = {
method: 'blocklist-update'
};
this.sendRequest(o, function () {
remote._controller.loadDaemonPrefs();
});
},
// Added queue calls
moveTorrentsToTop: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests(RPC._QueueMoveTop, torrent_ids, callback, context);
},
moveTorrentsToBottom: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests(RPC._QueueMoveBottom, torrent_ids, callback, context);
},
moveTorrentsUp: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests(RPC._QueueMoveUp, torrent_ids, callback, context);
},
moveTorrentsDown: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests(RPC._QueueMoveDown, torrent_ids, callback, context);
// set the Transmission-Session-Id on a 409
if (
request.status === 409 &&
(token = request.getResponseHeader('X-Transmission-Session-Id'))
) {
remote._token = token;
$.ajax(ajaxObject);
return;
}
remote._error = request.responseText
? request.responseText.trim().replace(/(<([^>]+)>)/gi, '')
: '';
if (!remote._error.length) {
remote._error = 'Server not responding';
}
dialog.confirm(
'Connection Failed',
'Could not connect to the server. You may need to reload the page to reconnect.',
'Details',
function () {
alert(remote._error);
},
'Dismiss'
);
remote._controller.togglePeriodicSessionRefresh(false);
},
appendSessionId: function (XHR) {
if (this._token) {
XHR.setRequestHeader('X-Transmission-Session-Id', this._token);
}
},
sendRequest: function (data, callback, context, async) {
const remote = this;
if (typeof async != 'boolean') {
async = true;
}
var ajaxSettings = {
url: RPC._Root,
type: 'POST',
contentType: 'json',
dataType: 'json',
cache: false,
data: JSON.stringify(data),
beforeSend: function (XHR) {
remote.appendSessionId(XHR);
},
error: function (request, error_string, exception) {
remote.ajaxError(request, error_string, exception, ajaxSettings);
},
success: callback,
context: context,
async: async,
};
$.ajax(ajaxSettings);
},
loadDaemonPrefs: function (callback, context, async) {
const o = {
method: 'session-get',
};
this.sendRequest(o, callback, context, async);
},
checkPort: function (callback, context, async) {
const o = {
method: 'port-test',
};
this.sendRequest(o, callback, context, async);
},
renameTorrent: function (torrentIds, oldpath, newname, callback, context) {
const o = {
method: 'torrent-rename-path',
arguments: {
ids: torrentIds,
path: oldpath,
name: newname,
},
};
this.sendRequest(o, callback, context);
},
loadDaemonStats: function (callback, context, async) {
const o = {
method: 'session-stats',
};
this.sendRequest(o, callback, context, async);
},
updateTorrents: function (torrentIds, fields, callback, context) {
const o = {
method: 'torrent-get',
arguments: {
fields: fields,
},
};
if (torrentIds) {
o['arguments'].ids = torrentIds;
}
this.sendRequest(o, function (response) {
const args = response['arguments'];
callback.call(context, args.torrents, args.removed);
});
},
getFreeSpace: function (dir, callback, context) {
const o = {
method: 'free-space',
arguments: {
path: dir,
},
};
this.sendRequest(o, function (response) {
const args = response['arguments'];
callback.call(context, args.path, args['size-bytes']);
});
},
changeFileCommand: function (torrentId, fileIndices, command) {
const remote = this,
args = {
ids: [torrentId],
};
args[command] = fileIndices;
this.sendRequest(
{
arguments: args,
method: 'torrent-set',
},
function () {
remote._controller.refreshTorrents([torrentId]);
}
);
},
sendTorrentSetRequests: function (method, torrent_ids, args, callback, context) {
if (!args) {
args = {};
}
args['ids'] = torrent_ids;
const o = {
method: method,
arguments: args,
};
this.sendRequest(o, callback, context);
},
sendTorrentActionRequests: function (method, torrent_ids, callback, context) {
this.sendTorrentSetRequests(method, torrent_ids, null, callback, context);
},
startTorrents: function (torrent_ids, noqueue, callback, context) {
const name = noqueue ? 'torrent-start-now' : 'torrent-start';
this.sendTorrentActionRequests(name, torrent_ids, callback, context);
},
stopTorrents: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests('torrent-stop', torrent_ids, callback, context);
},
moveTorrents: function (torrent_ids, new_location, callback, context) {
this.sendTorrentSetRequests(
'torrent-set-location',
torrent_ids,
{
move: true,
location: new_location,
},
callback,
context
);
},
removeTorrents: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests('torrent-remove', torrent_ids, callback, context);
},
removeTorrentsAndData: function (torrents) {
const remote = this;
const o = {
method: 'torrent-remove',
arguments: {
'delete-local-data': true,
ids: [],
},
};
if (torrents) {
for (let i = 0, len = torrents.length; i < len; ++i) {
o.arguments.ids.push(torrents[i].getId());
}
}
this.sendRequest(o, function () {
remote._controller.refreshTorrents();
});
},
verifyTorrents: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests('torrent-verify', torrent_ids, callback, context);
},
reannounceTorrents: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests('torrent-reannounce', torrent_ids, callback, context);
},
addTorrentByUrl: function (url, options) {
const remote = this;
if (url.match(/^[0-9a-f]{40}$/i)) {
url = 'magnet:?xt=urn:btih:' + url;
}
const o = {
method: 'torrent-add',
arguments: {
paused: options.paused,
filename: url,
},
};
this.sendRequest(o, function () {
remote._controller.refreshTorrents();
});
},
savePrefs: function (args) {
const remote = this;
const o = {
method: 'session-set',
arguments: args,
};
this.sendRequest(o, function () {
remote._controller.loadDaemonPrefs();
});
},
updateBlocklist: function () {
const remote = this;
const o = {
method: 'blocklist-update',
};
this.sendRequest(o, function () {
remote._controller.loadDaemonPrefs();
});
},
// Added queue calls
moveTorrentsToTop: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests(RPC._QueueMoveTop, torrent_ids, callback, context);
},
moveTorrentsToBottom: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests(RPC._QueueMoveBottom, torrent_ids, callback, context);
},
moveTorrentsUp: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests(RPC._QueueMoveUp, torrent_ids, callback, context);
},
moveTorrentsDown: function (torrent_ids, callback, context) {
this.sendTorrentActionRequests(RPC._QueueMoveDown, torrent_ids, callback, context);
},
};

View File

@ -8,111 +8,112 @@
function TorrentRendererHelper() {}
TorrentRendererHelper.getProgressInfo = function (controller, t) {
var pct, extra;
var s = t.getStatus();
var seed_ratio_limit = t.seedRatioLimit(controller);
let pct, extra;
const s = t.getStatus();
const seed_ratio_limit = t.seedRatioLimit(controller);
if (t.needsMetaData()) {
pct = t.getMetadataPercentComplete() * 100;
} else if (!t.isDone()) {
pct = Math.round(t.getPercentDone() * 100);
} else if (seed_ratio_limit > 0 && t.isSeeding()) { // don't split up the bar if paused or queued
pct = Math.round(t.getUploadRatio() * 100 / seed_ratio_limit);
} else {
pct = 100;
};
if (t.needsMetaData()) {
pct = t.getMetadataPercentComplete() * 100;
} else if (!t.isDone()) {
pct = Math.round(t.getPercentDone() * 100);
} else if (seed_ratio_limit > 0 && t.isSeeding()) {
// don't split up the bar if paused or queued
pct = Math.round((t.getUploadRatio() * 100) / seed_ratio_limit);
} else {
pct = 100;
}
if (s === Torrent._StatusStopped) {
extra = 'paused';
} else if (s === Torrent._StatusDownloadWait) {
extra = 'leeching queued';
} else if (t.needsMetaData()) {
extra = 'magnet';
} else if (s === Torrent._StatusDownload) {
extra = 'leeching';
} else if (s === Torrent._StatusSeedWait) {
extra = 'seeding queued';
} else if (s === Torrent._StatusSeed) {
extra = 'seeding';
} else {
extra = '';
};
if (s === Torrent._StatusStopped) {
extra = 'paused';
} else if (s === Torrent._StatusDownloadWait) {
extra = 'leeching queued';
} else if (t.needsMetaData()) {
extra = 'magnet';
} else if (s === Torrent._StatusDownload) {
extra = 'leeching';
} else if (s === Torrent._StatusSeedWait) {
extra = 'seeding queued';
} else if (s === Torrent._StatusSeed) {
extra = 'seeding';
} else {
extra = '';
}
return {
percent: pct,
complete: ['torrent_progress_bar', 'complete', extra].join(' '),
incomplete: ['torrent_progress_bar', 'incomplete', extra].join(' ')
};
return {
percent: pct,
complete: ['torrent_progress_bar', 'complete', extra].join(' '),
incomplete: ['torrent_progress_bar', 'incomplete', extra].join(' '),
};
};
TorrentRendererHelper.createProgressbar = function (classes) {
var complete, incomplete, progressbar;
let complete, incomplete, progressbar;
complete = document.createElement('div');
complete.className = 'torrent_progress_bar complete';
complete = document.createElement('div');
complete.className = 'torrent_progress_bar complete';
incomplete = document.createElement('div');
incomplete.className = 'torrent_progress_bar incomplete';
incomplete = document.createElement('div');
incomplete.className = 'torrent_progress_bar incomplete';
progressbar = document.createElement('div');
progressbar.className = 'torrent_progress_bar_container ' + classes;
progressbar.appendChild(complete);
progressbar.appendChild(incomplete);
progressbar = document.createElement('div');
progressbar.className = 'torrent_progress_bar_container ' + classes;
progressbar.appendChild(complete);
progressbar.appendChild(incomplete);
return {
'element': progressbar,
'complete': complete,
'incomplete': incomplete
};
return {
element: progressbar,
complete: complete,
incomplete: incomplete,
};
};
TorrentRendererHelper.renderProgressbar = function (controller, t, progressbar) {
var e, style, width, display;
var info = TorrentRendererHelper.getProgressInfo(controller, t);
let e, style, width, display;
const info = TorrentRendererHelper.getProgressInfo(controller, t);
// update the complete progressbar
e = progressbar.complete;
style = e.style;
width = '' + info.percent + '%';
display = info.percent > 0 ? 'block' : 'none';
if (style.width !== width || style.display !== display) {
$(e).css({
width: '' + info.percent + '%',
display: display
});
};
// update the complete progressbar
e = progressbar.complete;
style = e.style;
width = '' + info.percent + '%';
display = info.percent > 0 ? 'block' : 'none';
if (style.width !== width || style.display !== display) {
$(e).css({
width: '' + info.percent + '%',
display: display,
});
}
if (e.className !== info.complete) {
e.className = info.complete;
};
if (e.className !== info.complete) {
e.className = info.complete;
}
// update the incomplete progressbar
e = progressbar.incomplete;
display = (info.percent < 100) ? 'block' : 'none';
// update the incomplete progressbar
e = progressbar.incomplete;
display = info.percent < 100 ? 'block' : 'none';
if (e.style.display !== display) {
e.style.display = display;
};
if (e.style.display !== display) {
e.style.display = display;
}
if (e.className !== info.incomplete) {
e.className = info.incomplete;
};
if (e.className !== info.incomplete) {
e.className = info.incomplete;
}
};
TorrentRendererHelper.formatUL = function (t) {
return '▲' + Transmission.fmt.speedBps(t.getUploadSpeed());
return '▲' + Transmission.fmt.speedBps(t.getUploadSpeed());
};
TorrentRendererHelper.formatDL = function (t) {
return '▼' + Transmission.fmt.speedBps(t.getDownloadSpeed());
return '▼' + Transmission.fmt.speedBps(t.getDownloadSpeed());
};
TorrentRendererHelper.formatETA = function (t) {
var eta = t.getETA();
if (eta < 0 || eta >= (999 * 60 * 60)) {
return "";
};
return "ETA: " + Transmission.fmt.timeInterval(eta);
const eta = t.getETA();
if (eta < 0 || eta >= 999 * 60 * 60) {
return '';
}
return 'ETA: ' + Transmission.fmt.timeInterval(eta);
};
/****
@ -120,178 +121,208 @@ TorrentRendererHelper.formatETA = function (t) {
*****
****/
function TorrentRendererFull() {};
function TorrentRendererFull() {}
TorrentRendererFull.prototype = {
createRow: function () {
var root, name, peers, progressbar, details, image, button;
createRow: function () {
let root, name, peers, progressbar, details, image, button;
root = document.createElement('li');
root.className = 'torrent';
root = document.createElement('li');
root.className = 'torrent';
name = document.createElement('div');
name.className = 'torrent_name';
name = document.createElement('div');
name.className = 'torrent_name';
peers = document.createElement('div');
peers.className = 'torrent_peer_details';
peers = document.createElement('div');
peers.className = 'torrent_peer_details';
progressbar = TorrentRendererHelper.createProgressbar('full');
progressbar = TorrentRendererHelper.createProgressbar('full');
details = document.createElement('div');
details.className = 'torrent_progress_details';
details = document.createElement('div');
details.className = 'torrent_progress_details';
image = document.createElement('div');
button = document.createElement('a');
button.appendChild(image);
image = document.createElement('div');
button = document.createElement('a');
button.appendChild(image);
root.appendChild(name);
root.appendChild(peers);
root.appendChild(button);
root.appendChild(progressbar.element);
root.appendChild(details);
root.appendChild(name);
root.appendChild(peers);
root.appendChild(button);
root.appendChild(progressbar.element);
root.appendChild(details);
root._name_container = name;
root._peer_details_container = peers;
root._progress_details_container = details;
root._progressbar = progressbar;
root._pause_resume_button_image = image;
root._toggle_running_button = button;
root._name_container = name;
root._peer_details_container = peers;
root._progress_details_container = details;
root._progressbar = progressbar;
root._pause_resume_button_image = image;
root._toggle_running_button = button;
return root;
},
return root;
},
getPeerDetails: function (t) {
var err,
peer_count,
webseed_count,
fmt = Transmission.fmt;
getPeerDetails: function (t) {
let err,
peer_count,
webseed_count,
fmt = Transmission.fmt;
if ((err = t.getErrorMessage())) {
return err;
};
if (t.isDownloading()) {
peer_count = t.getPeersConnected();
webseed_count = t.getWebseedsSendingToUs();
if (webseed_count && peer_count) {
// Downloading from 2 of 3 peer(s) and 2 webseed(s)
return ['Downloading from',
t.getPeersSendingToUs(),
'of',
fmt.countString('peer', 'peers', peer_count),
'and',
fmt.countString('web seed', 'web seeds', webseed_count),
'',
TorrentRendererHelper.formatDL(t),
TorrentRendererHelper.formatUL(t)
].join(' ');
} else if (webseed_count) {
// Downloading from 2 webseed(s)
return ['Downloading from',
fmt.countString('web seed', 'web seeds', webseed_count),
'',
TorrentRendererHelper.formatDL(t),
TorrentRendererHelper.formatUL(t)
].join(' ');
} else {
// Downloading from 2 of 3 peer(s)
return ['Downloading from',
t.getPeersSendingToUs(),
'of',
fmt.countString('peer', 'peers', peer_count),
'',
TorrentRendererHelper.formatDL(t),
TorrentRendererHelper.formatUL(t)
].join(' ');
};
};
if (t.isSeeding()) {
return ['Seeding to', t.getPeersGettingFromUs(), 'of', fmt.countString('peer', 'peers', t.getPeersConnected()), '-', TorrentRendererHelper.formatUL(t)].join(' ');
};
if (t.isChecking()) {
return ['Verifying local data (', Transmission.fmt.percentString(100.0 * t.getRecheckProgress()), '% tested)'].join('');
}
return t.getStateString();
},
getProgressDetails: function (controller, t) {
if (t.needsMetaData()) {
var MetaDataStatus = "retrieving";
if (t.isStopped()) {
MetaDataStatus = "needs";
};
var percent = 100 * t.getMetadataPercentComplete();
return ["Magnetized transfer - " + MetaDataStatus + " metadata (",
Transmission.fmt.percentString(percent),
"%)"
].join('');
}
var c;
var sizeWhenDone = t.getSizeWhenDone();
var totalSize = t.getTotalSize();
var is_done = t.isDone() || t.isSeeding();
if (is_done) {
if (totalSize === sizeWhenDone) {
// seed: '698.05 MiB'
c = [Transmission.fmt.size(totalSize)];
} else { // partial seed: '127.21 MiB of 698.05 MiB (18.2%)'
c = [Transmission.fmt.size(sizeWhenDone), ' of ', Transmission.fmt.size(t.getTotalSize()), ' (', t.getPercentDoneStr(), '%)'];
};
// append UL stats: ', uploaded 8.59 GiB (Ratio: 12.3)'
c.push(', uploaded ',
Transmission.fmt.size(t.getUploadedEver()),
' (Ratio ',
Transmission.fmt.ratioString(t.getUploadRatio()),
')');
} else { // not done yet
c = [Transmission.fmt.size(sizeWhenDone - t.getLeftUntilDone()),
' of ', Transmission.fmt.size(sizeWhenDone),
' (', t.getPercentDoneStr(), '%)'
];
};
// maybe append eta
if (!t.isStopped() && (!is_done || t.seedRatioLimit(controller) > 0)) {
c.push(' - ');
var eta = t.getETA();
if (eta < 0 || eta >= (999 * 60 * 60) /* arbitrary */ ) {
c.push('remaining time unknown');
} else {
c.push(Transmission.fmt.timeInterval(t.getETA()), ' remaining');
};
};
return c.join('');
},
render: function (controller, t, root) {
// name
setTextContent(root._name_container, t.getName());
// progressbar
TorrentRendererHelper.renderProgressbar(controller, t, root._progressbar);
// peer details
var has_error = t.getError() !== Torrent._ErrNone;
var e = root._peer_details_container;
$(e).toggleClass('error', has_error);
setTextContent(e, this.getPeerDetails(t));
// progress details
e = root._progress_details_container;
setTextContent(e, this.getProgressDetails(controller, t));
// pause/resume button
var is_stopped = t.isStopped();
e = root._pause_resume_button_image;
e.alt = is_stopped ? 'Resume' : 'Pause';
e.className = is_stopped ? 'torrent_resume' : 'torrent_pause';
if ((err = t.getErrorMessage())) {
return err;
}
if (t.isDownloading()) {
peer_count = t.getPeersConnected();
webseed_count = t.getWebseedsSendingToUs();
if (webseed_count && peer_count) {
// Downloading from 2 of 3 peer(s) and 2 webseed(s)
return [
'Downloading from',
t.getPeersSendingToUs(),
'of',
fmt.countString('peer', 'peers', peer_count),
'and',
fmt.countString('web seed', 'web seeds', webseed_count),
'',
TorrentRendererHelper.formatDL(t),
TorrentRendererHelper.formatUL(t),
].join(' ');
} else if (webseed_count) {
// Downloading from 2 webseed(s)
return [
'Downloading from',
fmt.countString('web seed', 'web seeds', webseed_count),
'',
TorrentRendererHelper.formatDL(t),
TorrentRendererHelper.formatUL(t),
].join(' ');
} else {
// Downloading from 2 of 3 peer(s)
return [
'Downloading from',
t.getPeersSendingToUs(),
'of',
fmt.countString('peer', 'peers', peer_count),
'',
TorrentRendererHelper.formatDL(t),
TorrentRendererHelper.formatUL(t),
].join(' ');
}
}
if (t.isSeeding()) {
return [
'Seeding to',
t.getPeersGettingFromUs(),
'of',
fmt.countString('peer', 'peers', t.getPeersConnected()),
'-',
TorrentRendererHelper.formatUL(t),
].join(' ');
}
if (t.isChecking()) {
return [
'Verifying local data (',
Transmission.fmt.percentString(100.0 * t.getRecheckProgress()),
'% tested)',
].join('');
}
return t.getStateString();
},
getProgressDetails: function (controller, t) {
if (t.needsMetaData()) {
let MetaDataStatus = 'retrieving';
if (t.isStopped()) {
MetaDataStatus = 'needs';
}
const percent = 100 * t.getMetadataPercentComplete();
return [
'Magnetized transfer - ' + MetaDataStatus + ' metadata (',
Transmission.fmt.percentString(percent),
'%)',
].join('');
}
let c;
const sizeWhenDone = t.getSizeWhenDone();
const totalSize = t.getTotalSize();
const is_done = t.isDone() || t.isSeeding();
if (is_done) {
if (totalSize === sizeWhenDone) {
// seed: '698.05 MiB'
c = [Transmission.fmt.size(totalSize)];
} else {
// partial seed: '127.21 MiB of 698.05 MiB (18.2%)'
c = [
Transmission.fmt.size(sizeWhenDone),
' of ',
Transmission.fmt.size(t.getTotalSize()),
' (',
t.getPercentDoneStr(),
'%)',
];
}
// append UL stats: ', uploaded 8.59 GiB (Ratio: 12.3)'
c.push(
', uploaded ',
Transmission.fmt.size(t.getUploadedEver()),
' (Ratio ',
Transmission.fmt.ratioString(t.getUploadRatio()),
')'
);
} else {
// not done yet
c = [
Transmission.fmt.size(sizeWhenDone - t.getLeftUntilDone()),
' of ',
Transmission.fmt.size(sizeWhenDone),
' (',
t.getPercentDoneStr(),
'%)',
];
}
// maybe append eta
if (!t.isStopped() && (!is_done || t.seedRatioLimit(controller) > 0)) {
c.push(' - ');
const eta = t.getETA();
if (eta < 0 || eta >= 999 * 60 * 60 /* arbitrary */) {
c.push('remaining time unknown');
} else {
c.push(Transmission.fmt.timeInterval(t.getETA()), ' remaining');
}
}
return c.join('');
},
render: function (controller, t, root) {
// name
setTextContent(root._name_container, t.getName());
// progressbar
TorrentRendererHelper.renderProgressbar(controller, t, root._progressbar);
// peer details
const has_error = t.getError() !== Torrent._ErrNone;
let e = root._peer_details_container;
$(e).toggleClass('error', has_error);
setTextContent(e, this.getPeerDetails(t));
// progress details
e = root._progress_details_container;
setTextContent(e, this.getProgressDetails(controller, t));
// pause/resume button
const is_stopped = t.isStopped();
e = root._pause_resume_button_image;
e.alt = is_stopped ? 'Resume' : 'Pause';
e.className = is_stopped ? 'torrent_resume' : 'torrent_pause';
},
};
/****
@ -299,79 +330,84 @@ TorrentRendererFull.prototype = {
*****
****/
function TorrentRendererCompact() {};
function TorrentRendererCompact() {}
TorrentRendererCompact.prototype = {
createRow: function () {
var progressbar, details, name, root;
createRow: function () {
let progressbar, details, name, root;
progressbar = TorrentRendererHelper.createProgressbar('compact');
progressbar = TorrentRendererHelper.createProgressbar('compact');
details = document.createElement('div');
details.className = 'torrent_peer_details compact';
details = document.createElement('div');
details.className = 'torrent_peer_details compact';
name = document.createElement('div');
name.className = 'torrent_name compact';
name = document.createElement('div');
name.className = 'torrent_name compact';
root = document.createElement('li');
root.appendChild(progressbar.element);
root.appendChild(details);
root.appendChild(name);
root.className = 'torrent compact';
root._progressbar = progressbar;
root._details_container = details;
root._name_container = name;
return root;
},
root = document.createElement('li');
root.appendChild(progressbar.element);
root.appendChild(details);
root.appendChild(name);
root.className = 'torrent compact';
root._progressbar = progressbar;
root._details_container = details;
root._name_container = name;
return root;
},
getPeerDetails: function (t) {
var c;
if ((c = t.getErrorMessage())) {
return c;
};
if (t.isDownloading()) {
var have_dn = t.getDownloadSpeed() > 0;
var have_up = t.getUploadSpeed() > 0;
if (!have_up && !have_dn) {
return 'Idle';
};
var s = '';
if (!isMobileDevice) {
s = TorrentRendererHelper.formatETA(t) + ' ';
};
if (have_dn) {
s += TorrentRendererHelper.formatDL(t);
};
if (have_dn && have_up) {
s += ' ';
};
if (have_up) {
s += TorrentRendererHelper.formatUL(t);
};
return s;
};
if (t.isSeeding()) {
return ['Ratio: ', Transmission.fmt.ratioString(t.getUploadRatio()), ', ', TorrentRendererHelper.formatUL(t)].join('');
};
return t.getStateString();
},
render: function (controller, t, root) {
// name
var is_stopped = t.isStopped();
var e = root._name_container;
$(e).toggleClass('paused', is_stopped);
setTextContent(e, t.getName());
// peer details
var has_error = t.getError() !== Torrent._ErrNone;
e = root._details_container;
$(e).toggleClass('error', has_error);
setTextContent(e, this.getPeerDetails(t));
// progressbar
TorrentRendererHelper.renderProgressbar(controller, t, root._progressbar);
getPeerDetails: function (t) {
let c;
if ((c = t.getErrorMessage())) {
return c;
}
if (t.isDownloading()) {
const have_dn = t.getDownloadSpeed() > 0;
const have_up = t.getUploadSpeed() > 0;
if (!have_up && !have_dn) {
return 'Idle';
}
let s = '';
if (!isMobileDevice) {
s = TorrentRendererHelper.formatETA(t) + ' ';
}
if (have_dn) {
s += TorrentRendererHelper.formatDL(t);
}
if (have_dn && have_up) {
s += ' ';
}
if (have_up) {
s += TorrentRendererHelper.formatUL(t);
}
return s;
}
if (t.isSeeding()) {
return [
'Ratio: ',
Transmission.fmt.ratioString(t.getUploadRatio()),
', ',
TorrentRendererHelper.formatUL(t),
].join('');
}
return t.getStateString();
},
render: function (controller, t, root) {
// name
const is_stopped = t.isStopped();
let e = root._name_container;
$(e).toggleClass('paused', is_stopped);
setTextContent(e, t.getName());
// peer details
const has_error = t.getError() !== Torrent._ErrNone;
e = root._details_container;
$(e).toggleClass('error', has_error);
setTextContent(e, this.getPeerDetails(t));
// progressbar
TorrentRendererHelper.renderProgressbar(controller, t, root._progressbar);
},
};
/****
@ -380,37 +416,36 @@ TorrentRendererCompact.prototype = {
****/
function TorrentRow(view, controller, torrent) {
this.initialize(view, controller, torrent);
};
this.initialize(view, controller, torrent);
}
TorrentRow.prototype = {
initialize: function (view, controller, torrent) {
var row = this;
this._view = view;
this._torrent = torrent;
this._element = view.createRow();
this.render(controller);
$(this._torrent).bind('dataChanged.torrentRowListener', function () {
row.render(controller);
});
},
getElement: function () {
return this._element;
},
render: function (controller) {
var tor = this.getTorrent();
if (tor) {
this._view.render(controller, tor, this.getElement());
};
},
isSelected: function () {
return this.getElement().className.indexOf('selected') !== -1;
},
getTorrent: function () {
return this._torrent;
},
getTorrentId: function () {
return this.getTorrent().getId();
initialize: function (view, controller, torrent) {
const row = this;
this._view = view;
this._torrent = torrent;
this._element = view.createRow();
this.render(controller);
$(this._torrent).bind('dataChanged.torrentRowListener', function () {
row.render(controller);
});
},
getElement: function () {
return this._element;
},
render: function (controller) {
const tor = this.getTorrent();
if (tor) {
this._view.render(controller, tor, this.getElement());
}
},
isSelected: function () {
return this.getElement().className.indexOf('selected') !== -1;
},
getTorrent: function () {
return this._torrent;
},
getTorrentId: function () {
return this.getTorrent().getId();
},
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff