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:
parent
a4dd67ae45
commit
be219ddee0
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6,4 +6,5 @@ services:
|
|||
volumes:
|
||||
- .:/src
|
||||
- ./code_style.sh:/code_style.sh:ro
|
||||
- ./format:/format:ro
|
||||
command: ["/bin/sh", "/code_style.sh", "/src"]
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
FROM alpine
|
||||
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
git \
|
||||
npm \
|
||||
perl \
|
||||
uncrustify \
|
||||
&& npm install -g \
|
||||
js-beautify
|
||||
eslint \
|
||||
prettier
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
#
|
|
@ -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); \
|
||||
} \
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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, "∞" 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, "∞" 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, "<").replace(/>/g, ">");
|
||||
};
|
||||
return text.replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 '∞';
|
||||
}
|
||||
return this.percentString(x);
|
||||
},
|
||||
|
||||
/*
|
||||
* Format a ratio to a string
|
||||
*/
|
||||
ratioString: function (x) {
|
||||
if (x === -1) {
|
||||
return "None";
|
||||
}
|
||||
if (x === -2) {
|
||||
return '∞';
|
||||
}
|
||||
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
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
})();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue