Vehicles Search + Vehicles Pin + Faces Recognition

сегодня день обновлений
This commit is contained in:
themohooks 2024-10-15 20:39:44 +03:00
parent 5d9e930d30
commit 42730bac08
14 changed files with 1038 additions and 54 deletions

View file

@ -1,11 +1,11 @@
<?php
namespace App\Controllers\Api\Images\Comments;
namespace App\Controllers\Api\Vehicles;
use App\Services\{Auth, Router, GenerateRandomStr, DB, Json, EXIF};
use App\Models\{User, Vote, Comment};
use App\Models\{User, Vote, Comment, Vehicle};
class Load
@ -13,7 +13,36 @@ class Load
public function __construct()
{
$comments = DB::query('SELECT * FROM entities_data WHERE photo_id=:pid', array(':pid' => explode('/', $_SERVER['REQUEST_URI'])[4]));
$entities_data = DB::query('SELECT * FROM entities_data WHERE (LOWER(title) LIKE :value) OR (LOWER(id) LIKE :value) AND entityid=:pid', array(':pid' => $_GET['type'], ':value'=>'%'.$_GET['num'].'%'));
echo '<table>';
foreach ($entities_data as $e) {
$vehicle = new Vehicle($e['entityid']);
echo '<tbody class="found_vehicle s11" data-state="1" data-vid="'.$e['id'].'" data-cid="2" data-type="3" data-twoside="0">
<tr>
<th width="100">ID</th>
<th width="90%">Название</th>
<th>Примечание</th>
<th class="c nw">Тип</th>
</tr>
<tr>
<td style="padding:10px"><a href="/vehicle/'.$e['id'].'" target="_blank" class="num pcnt">'.$e['id'].'</a>
</td>
<td style="padding:10px; font-size:16px" class="mname">hhhhh</td>
<td style="padding:10px" class="d">
'.$e['comment'].'
</td>
<td style="padding:10px" class="d">
'.$vehicle->i('title').'
</td>
</tr>
}
}
</tr>
</tbody>';
}
echo '</table>';
}
}

View file

@ -18,6 +18,7 @@ use \App\Controllers\Api\Images\Comments\Edit as PhotoCommentEdit;
use \App\Controllers\Api\Images\Comments\Delete as PhotoCommentDelete;
use \App\Controllers\Api\Images\Comments\Load as PhotoCommentLoad;
use \App\Controllers\Api\Images\Comments\Rate as PhotoCommentVote;
use \App\Controllers\Api\Vehicles\Load as VehiclesLoad;
use \App\Controllers\Api\Profile\Update as ProfileUpdate;
use \App\Controllers\Api\Users\LoadUser as UserLoad;
use \App\Controllers\Api\Admin\Images\SetVisibility as AdminPhotoSetVisibility;
@ -91,6 +92,9 @@ class ApiController
public static function admingetvehicleinputs() {
return new AdminGetVehicleInputs();
}
public static function vehiclesload() {
return new VehiclesLoad();
}
}

View file

@ -61,6 +61,7 @@ class Routes
Router::get('/api/photo/comment/rate', 'ApiController@photocommentvote');
Router::post('/api/photo/comment/$id/edit', 'ApiController@photocommentedit');
Router::post('/api/photo/comment/$id/delete', 'ApiController@photocommentdelete');
Router::get('/api/vehicles/load', 'ApiController@vehiclesload');
if ($user->i('admin') > 0) {
Router::any('/admin', 'AdminController@index');
Router::any('/api/admin/images/setvisibility', 'ApiController@adminsetvis');

View file

@ -13,7 +13,6 @@ class Vote
if ($type < 0) {
$type = -1;
}
return $type;
} else {
return -1;

View file

@ -98,6 +98,36 @@ a:hover {
background-color:var(--theme-link-hover-bg-color);
}
input:checked[type=checkbox] {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e") !important;
}
input:checked {
background-color: #292929 !important;
}
input[type=checkbox]{
border-radius: 2px;
width: 1em;
height: 1em;
margin-top: .25em;
vertical-align: top;
background-color: #fff;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
border: 1px solid rgba(0, 0, 0, .25);
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
-webkit-print-color-adjust: exact;
color-adjust: exact;
print-color-adjust: exact;
}
a.und {
border-bottom:solid 1px var(--theme-link-underline-color);
}

View file

@ -1,7 +1,86 @@
var gal_cid = -1;
var new_vehicle_idx = 0;
var modified = false;
var cnames = {2: 'Санкт-Петербург'};
var binds = [
{ value: 1, item: '<b>Основная — ТС на переднем плане</b>', label: '<b>Основная</b>' },
{ value: 0, item: 'Второстепенная — ТС на заднем плане', label: 'Второстепенная' },
{ value: 2, item: '<i>Условная — ТС указано предположительно</i>', label: '<i>Условная</i>' }
];
addTexts({
'UP_WRONGTYPE': 'Недопустимый тип файла',
'UP_TOOSMALL': 'Выбранное изображение слишком маленькое — его длина по широкой стороне составляет %d пикселей. Для загрузки на сайт она должна быть не менее %d пикселей',
'UP_OVERSIZE_JPG': 'Выбранное изображение слишком большое — сумма его ширины и высоты составляет %d пикселей. Для изображений JPEG и WEBP она не должна превышать %d пикселей',
'UP_OVERSIZE_PNG': 'Выбранное изображение слишком большое — его длина по широкой стороне составляет %d пикселей. Для изображений GIF и PNG она не должна превышать %d пикселей',
'UP_LARGEFILE_JPG': 'Этот файл слишком большой — %d КБ. Вы можете загружать файлы JPEG и WEBP объёмом до %d КБ',
'UP_LARGEFILE_PNG': 'Этот файл слишком большой — %d МБ. Вы можете загружать файлы GIF и PNG объёмом до %d МБ',
'UP_NEEDRESIZE': 'Выбранное фото превышает %d пикселей по сумме ширины и высоты,<br />поэтому оно будет уменьшено до %d пикселей по широкой стороне',
'UP_NULART': '(подходящей галереи нет в списке, требуется создать новую)',
'UP_LOADING': 'Загрузка...',
'UP_SEARCHING': 'Идёт поиск...',
'UP_OTHER': 'Новая',
'UP_NOFILE': 'Не выбран файл для загрузки',
'UP_NOCOORDS': 'Поместите маркер на карте в точку, с которой вы производили съёмку. Для установки маркера достаточно кликнуть по карте в нужном месте.',
'UP_NODIR': 'Укажите направление съёмки.',
'UP_NOCITY': 'Вы не указали город. Нажмите кнопку подтверждения для отправки фотографии, если города съемки нет в списке. Нажмите кнопку отмены, если Вы забыли указать город.',
'UP_OTHERCITY': 'Город съёмки не соответствует привязанным ТС и/или галереям. Продолжить отправку фото?',
'UP_ERROR': 'Фото не было загружено :-(',
'UP_SUCCESS': 'Фотография успешно загружена!',
'UP_NOTHING': 'К сожалению, ничего подходящего найти не удалось',
'UP_V_LINKED': 'Это ТС уже привязано к данному фото',
'UP_G_LINKED': 'Эта галерея уже привязана к данному фото',
'UP_CREATIVE': 'Фотография, загружаемая в «Фотозарисовки» или «Художественную галерею», не может быть привязана к ТС и другим галереям.',
'UP_NOLINKS': 'Фотография ни к чему не привязана',
'UP_NO_PRI': 'Фотография не может иметь только второстепенные привязки.',
'UP_NODATE': 'Вы не указали дату снимка. Нажмите «OK», если так и должно быть, или «Отмена», если хотите добавить дату съёмки',
'UP_NOPLACE': 'Вы не указали место съёмки. Нажмите «OK», если так и должно быть, или «Отмена», если хотите добавить место съёмки',
'UP_ARTICLE': 'Галерея',
'UP_LIMITEXC': 'Сегодня Вы уже загрузили максимально возможное число фотографий. Следующие фотографии Вы можете загрузить завтра',
'UP_ROUTE': 'Маршрут',
'UP_NOTES': 'примечание',
'VIEW': 'Ракурс',
'UP_NOVIEW': 'Не для всех ТС указан ракурс съёмки.',
'UP_TOQUEUE': 'Это фото не может быть опубликовано без модерации, поэтому оно было помещено в очередь',
'UP_BIND': 'Привязка',
'UP_BIND_PRI': 'Основная — ТС на переднем плане',
'UP_BIND_SEC': 'Второстепенная — ТС на заднем плане',
'UP_BIND_CON': 'Условная — ТС указано предположительно',
'UP_NAA_ALLOW_NO': 'Не указано разрешение на публикацию.',
'UP_TWOSIDE': 'Вы выбрали тип ПС (двухсторонний/односторонний), не совпадающий с указанным для данной модели. Так и должно быть?',
'MAP_SEARCH': 'Адрес или объект...',
'MAP_NOTFOUND': 'На карте не удалось найти указанное место.',
'MAP_OSM': 'Карта OpenStreetMap',
'MAP_OSM_BW': 'Чёрно-белая карта OpenStreetMap',
'MAP_OSM_HOT': 'Карта Humanitarian OpenStreetMap Team',
'MAP_TOPO': 'Карта OpenTopoMap',
'MAP_WIKIMEDIA': 'Карта Wikimedia',
'MAP_OPNV': 'Карта ÖPNVKarte',
'MAP_OPENPTMAP': 'Общественный транспорт от OpenPtMap',
'MAP_RAILWAY': 'Железная дорога от OpenRailwayMap',
'MAP_BING': 'Спутник Bing',
'MAP_YANDEX': 'Карта Яндекс',
'MAP_YANDSAT': 'Спутник Яндекс'
});
var views = {
0: '<span class="s5">&nbsp;Не указан&nbsp;</span>',
1: 'Спереди-справа (двери)',
2: 'Спереди-слева (окна)',
3: 'Сзади-справа (двери)',
4: 'Сзади-слева (окна)',
5: 'Вид строго спереди',
6: 'Правый борт',
7: 'Вид строго сзади',
8: 'Левый борт',
9: 'Салон, вид вперёд',
10: 'Салон, вид назад',
11: 'Кабина',
12: 'Заводская табличка',
13: 'Отдельные элементы ТС',
14: 'Не определяется (двухстороннее ТС)',
20: 'Вид сверху',
40: 'Вид снизу'};
$(document).ready(function()
{
@ -42,29 +121,21 @@ $(document).ready(function()
var html = '<tbody data-nid="' + nid + '" data-vid="' + vid + '" data-twoside="' + $(this).data('twoside') + '" class="s' + $(this).data('state') + '">\n';
html += '<tr>\n';
html += '<td style="padding:3px 10px 5px"><input type="hidden" name="nids[]" value="' + nid + '"><input type="hidden" name="cids[]" value="' + cid + '"><a href="' + (nid > 0 ? '/vehicle/' + vid + '/#n' + nid : '/lk/vehicles.php?action=edit&amp;vid=' + (-nid)) + '" target="_blank" class="num pcnt">' + $('.num', this).html() + '</a></td>\n';
html += '<td style="padding:3px 10px 5px"><input type="hidden" name="nids[]" value="' + nid + '"><input type="hidden" name="cids[]" value="' + cid + '"><a href="' + (nid > 0 ? '/vehicle/' + vid : '/lk/vehicles.php?action=edit&amp;vid=' + (-nid)) + '" target="_blank" class="num pcnt">' + $('.num', this).html() + '</a></td>\n';
html += '<td style="padding:3px 10px 6px">' + $('.mname', this).html() + '</td>\n';
html += '<td style="padding:3px 0 6px 10px; color:#777" class="r">' + _text['UP_ROUTE'] + ':</td>\n';
html += '<td style="padding:3px 7px" class="nw"><input type="text" class="route" name="route[' + nid + ']" style="width:40px; font-weight:bold; text-align:center" maxlength="7" value="">, <input type="text" class="notes" name="notes[' + nid + ']" style="width:170px" maxlength="100" value="" placeholder="' + _text['UP_NOTES'] + '"></td>\n';
html += '<td class="r"><a href="#" class="delLink" style="font-size:16px">&times;</a></td>\n';
html += '</tr>\n';
html += '<tr>\n';
html += '<td style="padding:0 12px 7px" colspan="2"><a href="/city/' + cid + '/" target="_blank">' + cname + '</a></td>\n';
html += '<td style="padding:0 0 7px; color:#777" class="r">' + _text['VIEW'] + ':</td>\n';
html += '<td style="padding:0 7px 7px" colspan="2"><input type="hidden" class="view" name="view[' + nid + ']" value="0"><a href="#" class="view_link dot">' + views[0] + '</a></td>\n';
html += '</tr>\n';
html += '<tr>\n';
html += '<td colspan="2"></td>\n';
html += '<td style="padding:0 0 7px; color:#777" class="r">' + _text['UP_BIND'] + ':</td>\n';
html += '<td style="padding:0 7px 7px" colspan="2"><input type="hidden" name="pri[' + nid + ']" class="pri-value" value="1"><a class="pri-label dot" href="#">' + binds[0].label + '</a></td>\n';
html += '</tr>\n';
html += '</tbody>\n';
var row = $(html);
$('#conn_veh').append(row).show().tablesort('recountRows');
$('#conn_veh').append(row).show();
$('.pri-label', row).selector2(binds);
$('.no-links').hide();
@ -84,6 +155,11 @@ $(document).ready(function()
setTimeout(function() { $('#conn_veh tbody[data-nid="' + nid + '"] .view_link').click(); }, 100);
});
$('#vlist').on('mouseenter mouseleave', '#add_new_vehicle', function()
{
var state = parseInt($(this).data('state'));
$(this).toggleClass('s' + state + ' s' + (state+10));
})
@ -225,12 +301,12 @@ function searchVehicles(by_gos)
$('#search_cid, #search_type, #search_num, #search_gos').prop('disabled', true);
$('#vlist').html('<div class="nw" style="padding:6px 10px">' + _text['UP_SEARCHING'] + '</div>').show();
var data = { cid: $('#search_cid').val(), type: $('#search_type').val(), pub_pid: pub_pid };
var data = { cid: $('#search_cid').val(), type: $('#search_type').val() };
if (!by_gos)
data.num = $('#search_num').val().trim();
else data.gos = $('#search_gos').val().trim();
$.get('/api.php?action=upload-search-vehicles', data, function (r)
$.get('/api/vehicles/load', data, function (r)
{
$('#vlist').html(r);
$('#search_cid, #search_type, #search_num, #search_gos').prop('disabled', false);
@ -238,6 +314,17 @@ function searchVehicles(by_gos)
return false;
}
document.onclick = function(e)
{
e = e || window.event;
E = e.target || e.srcElement;
if (E.id != 'phint' && E.parentNode.id != 'phint' && E != _getID('mform').place) $('#phint').slideUp();
if (E.className != 'searchVehiclesBtn' && E.id != 'vlist_table' && E.className != 'num' && $('#vlist').css('display') == 'block') $('#vlist').hide().html('');
if ($(E).closest('#views-selector').length == 0) $('#views-selector').hide();
};
@ -300,7 +387,6 @@ function showDefaultCity()
keys = Object.keys(cnames);
$('#main-cid').val(keys[0]);
$('#main-cname').val(cnames[keys[0]]);
selectCity(keys[0]);
}
}
}

211
static/js/selector.js Normal file
View file

@ -0,0 +1,211 @@
function hlText(text, val)
{
val = val.replace(' -', ' —');
var p = text.toLowerCase().indexOf(val.toLowerCase());
if (p != -1) text = text.substring(0, p) + '<span class="hl">' + text.substring(p, p + val.length) + '</span>' + text.substring(p + val.length);
return text;
}
$(document).ready(function() { $('head').append('<link rel="stylesheet" href="/css/ui-lightness/jquery-ui-1.8.20.custom.css">'); });
(function($)
{
$.fn.autocompleteSelector = function(valfield, query, options)
{
if (this.length == 0) return;
function idToJQ(id)
{
if (!id) return null;
if (id instanceof jQuery) return id;
if (typeof id === 'string' || id instanceof String) return $('#' + id);
return $(id);
}
var valueField = idToJQ(valfield);
var labelField = this;
this.savedLabel = this.val();
var makeBold = (this.css('font-weight') == '700');
var defaults = {
minLength: 2,
params: {},
paramsCallback: null,
selectCallback: null,
focusCallback: null,
blurCallback: null,
renderItem: null,
defaultValue: 0,
defaultLabel: null,
valueName: 'value',
labelName: 'label',
flag: null,
flagValueName: 'rid',
flagLabelName: null,
hlFlag: false,
clearField: false,
method: 'get'
};
if (options == undefined) options = {};
var opts = $.extend({}, defaults, options);
var flagImg;
if (!opts.flag)
{
if (opts.flagLabelName && valfield && (typeof valfield === 'string' || valfield instanceof String))
{
flagImg = $('#rid_' + valfield);
if (flagImg.length == 0) flagImg = null;
}
}
else flagImg = idToJQ(opts.flag);
var xsign;
if (valueField && opts.defaultLabel)
{
xsign = $('<div class="xsign" />');
xsign.insertAfter(labelField).on('click', function()
{
valueField.val(opts.defaultValue);
labelField.val(opts.defaultLabel).trigger('item-select');
if (opts.selectCallback) opts.selectCallback(null);
});
var paddingRight = parseInt(labelField.css('padding-right'));
function labelFieldChange()
{
if (labelField.val().trim() != opts.defaultLabel.trim())
{
labelField.css('padding-right', (paddingRight + 22) + 'px');
xsign.show();
}
else
{
labelField.css('padding-right', paddingRight);
xsign.hide();
}
}
labelField.on('item-select', labelFieldChange);
labelFieldChange();
}
else xsign = null;
this.autocomplete({
minLength: opts.minLength,
source: function(request, response)
{
if (opts.paramsCallback) opts.paramsCallback(opts.params);
opts.params.term = request.term;
if (opts.method == 'post')
$.post(query, opts.params, response, 'json').fail(function(jx) { if (jx.responseText != '') alert(jx.responseText); });
else $.get(query, opts.params, response, 'json').fail(function(jx) { if (jx.responseText != '') alert(jx.responseText); });
},
focus: function(event, ui)
{
if (event.pageX == undefined)
{
labelField.val(ui.item.label);
return false;
}
},
select: function(event, ui)
{
if (valueField) valueField.val(ui.item[opts.valueName]);
if (!opts.clearField)
{
var label = $('<div />').html(ui.item[opts.labelName]).text();
labelField.val(label).trigger('item-select');
labelField.savedLabel = label;
if (flagImg) flagImg.attr('src', '/img/r/' + ui.item[opts.flagValueName] + '.gif');
if (makeBold) labelField.css('font-weight', 'bold');
}
else labelField.val(labelField.savedLabel);
if (opts.selectCallback) opts.selectCallback(ui.item);
return false;
}
})
.focus(function()
{
if (valueField)
{
labelField.savedLabel = labelField.val();
if (makeBold) labelField.css('font-weight', 'normal');
}
if (opts.focusCallback) opts.focusCallback();
})
.blur(function()
{
var val = labelField.val().trim();
if (opts.defaultLabel && val == '')
{
if (valueField) valueField.val(opts.defaultValue);
labelField.val(opts.defaultLabel).trigger('item-select');
if (flagImg) flagImg.attr('src', '/img/r/0.gif');
if (opts.selectCallback) opts.selectCallback(null);
}
else
if (val != labelField.savedLabel) labelField.val(labelField.savedLabel);
if (valueField && makeBold) labelField.css('font-weight', 'bold');
if (opts.blurCallback) opts.blurCallback();
});
if (!opts.renderItem)
{
if (!opts.flagLabelName)
opts.renderItem = function(ul, item) { return $('<li><a><b>' + hlText(item[opts.labelName], this.element.val()) + '</b></a></li>').appendTo(ul); };
else opts.renderItem = function(ul, item) { return $('<li><a><div style="float:right; position:relative; top:1px" class="sm">&nbsp; &nbsp;' + (item[opts.flagLabelName] != undefined ? (opts.hlFlag ? hlText(item[opts.flagLabelName], this.element.val()) : item[opts.flagLabelName]) + ' &nbsp;' : '') + '<img src="/img/r/' + item.rid + '.gif" style="position:relative; top:-1px"></div><b>' + hlText(item[opts.labelName], this.element.val()) + '</b></a></li>').appendTo(ul); };
}
this.data('ui-autocomplete')._renderItem = opts.renderItem;
this.on('click', function() { if (labelField.savedLabel == this.value) { this.select(); } });
return this;
};
$.fn.citySelector = function(valfield, options)
{
if (options == undefined) options = {};
options.flagLabelName = 'rname';
options.hlFlag = true;
this.autocompleteSelector(valfield, '/api.php?action=get-cities', options);
};
$.fn.groupSelector = function(valfield, options)
{
if (options == undefined) options = {};
options.flagLabelName = 'rname';
options.hlFlag = true;
this.autocompleteSelector(valfield, '/api.php?action=get-groups', options);
};
$.fn.regionSelector = function(valfield, options)
{
if (options == undefined) options = {};
options.flagValueName = 'value';
if (!options.renderItem) options.renderItem = function(ul, item) { return $('<li><a style="line-height:14px; padding:3px 5px"><img src="/img/r/' + item.value + '.gif"> <b>' + hlText(item.label, this.element.val()) + '</b></a></li>').appendTo(ul); };
this.autocompleteSelector(valfield, '/api.php?action=get-regions', options);
};
$.fn.autocompleteHL = function(options)
{
this.autocomplete(options).data('ui-autocomplete')._renderItem = function(ul, item) { return $('<li><a style="line-height:14px; padding:3px 5px" class="sm">' + hlText(item.label, this.element.val()) + '</a></li>').appendTo(ul); };
}
})(jQuery);

97
static/js/selector2.js Normal file
View file

@ -0,0 +1,97 @@
(function($)
{
$.fn.selector2 = function(items, options)
{
if (this.length == 0) return;
this.each(function()
{
var labelElement = $(this);
var valueField = $('input', labelElement.parent());
var currentIndex = -1;
var defaults = {
selectCallback: null
};
if (options == undefined) options = {};
var opts = $.extend({}, defaults, options);
labelElement.on('click', function()
{
var currentValue = valueField.val();
var val, html = '<div class="selector2-helper">';
for (var i = 0; i < items.length; i++)
{
html += '<div data-index="' + i + '" data-value="' + items[i].value + '"';
if (items[i].value == currentValue)
{
currentIndex = i;
html += ' class="hov"';
}
html += '>' + (items[i].item || items[i].label) + '</div>';
}
html += '</div>';
var helper = $(html).appendTo('body');
var offset = $(this).offset();
helper.css('top', Math.max(0, offset.top - currentIndex * 22 - 1) + 'px');
helper.css('left', (offset.left - 7) + 'px');
helper.on('click', '> div', function()
{
var el = $(this);
var val = el.data('value');
var idx = el.data('index');
valueField.val(val);
labelElement.html(items[idx].label);
if (opts.selectCallback) opts.selectCallback.call(labelElement, val);
helper.remove();
$(document).off('.selector2');
})
.on('mouseenter', '> div', function() { var el = $(this); el.addClass('hov').siblings().removeClass('hov'); currentIndex = el.data('index'); });
$(document).on('click.selector2', function(e)
{
if (!$(e.target).is(helper)) helper.remove();
$(document).off('.selector2');
})
.on('keydown.selector2', function(e)
{
if (e.which == 40 || e.which == 38)
{
e.preventDefault();
$('> div', helper).removeClass('hov');
if (e.which == 40 && ++currentIndex == items.length) currentIndex = 0; else
if (e.which == 38 && --currentIndex < 0) currentIndex = items.length - 1;
$('div[data-index="' + currentIndex + '"]', helper).addClass('hov');
}
else
if ((e.which == 13 || e.which == 32) && currentIndex != -1)
{
e.preventDefault();
$('div[data-index="' + currentIndex + '"]', helper).trigger('click');
}
else
if (e.which == 27 || e.which == 8)
{
e.preventDefault();
helper.remove();
$(document).off('.selector2');
}
});
helper.show();
return false;
});
});
return this;
};
})(jQuery);

193
static/js/tablesort.js Normal file
View file

@ -0,0 +1,193 @@
/**
* tablesort plugin for jQuery
* Written by Alexander Konov
* Based on TableDnD ideas (https://github.com/isocra/TableDnD)
*/
(function($)
{
$.fn.tablesort = function(options)
{
function recountRows(tbl)
{
var opts = tbl.data('opts');
var rows = $(opts.rowSelector, tbl);
var rowsCount = rows.length;
var handle = (opts.dragHandle ? $(opts.dragHandle, tbl) : tbl);
tbl.data('rowsCount', rowsCount);
handle[rowsCount > 1 ? 'addClass' : 'removeClass']('tablesort-active');
return rows;
}
$(this).each(function()
{
var table = $(this);
// Метод recountRows нужно вызывать извне при добавлении или удалении строк в таблице
// Это требуется для автоотключения сортировки, если в таблице только одна строка
if (typeof options == 'string' && options == 'recountRows')
{
recountRows(table);
return table;
}
var eventStart = 'touchstart.tablesort mousedown.tablesort';
var eventMove = 'touchmove.tablesort mousemove.tablesort';
var eventEnd = 'touchend.tablesort mouseup.tablesort';
var dragObject = null;
var mouseOffset = null;
var oldY = 0;
var defaults = {
rowSelector: 'tr', // Можно указать tbody, если надо перемещать блоки, состоящие из нескольких строк
dragHandle: null, // Селектор "ручки" для переноса (по умолчанию можно перемещать, потянув за любое место строки, кроме <a> и <input>)
sensitivity: 10, // Sensitivity setting will throttle the trigger rate for movement detection
onChange: null // Функция onChange
};
if (options == undefined) options = {};
var opts = $.extend({}, defaults, options);
table.data('opts', opts);
if (opts.dragHandle)
table.on(eventStart, opts.dragHandle, function(e) { startDrag($(this).closest(opts.rowSelector), e); });
else table.on(eventStart, opts.rowSelector, function(e) { startDrag($(this), e); });
recountRows(table);
function startDrag(dragObj, e)
{
if (table.data('rowsCount') <= 1) return; // Некуда перетаскивать - всего одна строка в таблице
var target = $(e.target);
if (target.closest('a').length != 0 || target.is('input') || target.is('select') || target.is('textarea')) return; // Неподходящие для перетаскивания элементы с собственным поведением
e.preventDefault();
dragObject = dragObj;
mouseOffset = getMouseY(e) - $(e.target).offset().top;
$(document).on(eventMove, mouseMove).on(eventEnd, mouseUp);
$('html').add(opts.dragHandle ? $(opts.dragHandle, table) : table).addClass('tablesort-dragging');
toggleObjectClass(dragObject);
}
function toggleObjectClass(obj)
{
var s = obj.attr('class');
if (!s) return;
var i, cls, arr = s.split(' ');
for (i = 0; i < arr.length; i++)
{
if (/s[0-9]{1,2}/.test(arr[i]))
{
cls = parseInt(arr[i].substr(1));
arr[i] = 's' + (cls >= 10 && cls <= 19 || cls >= 30 && cls <= 39 ? cls-10 : cls+10);
}
obj.attr('class', arr.join(' '));
}
}
function getMouseY(e)
{
if (e.originalEvent.changedTouches) return e.originalEvent.changedTouches[0].clientY + $(document).scrollTop();
if (e.pageY) return e.pageY;
return e.clientY + $(document).scrollTop();
}
function checkPageScroll(e)
{
var y = (e.originalEvent.changedTouches) ? e.originalEvent.changedTouches[0].clientY : e.clientY;
var h = $(window).innerHeight();
if (y < 25) window.scrollBy(0, -10); else
if (y > h - 25) window.scrollBy(0, 10);
}
function rowMoving(dir, currentRow)
{
if (!dir || !currentRow) return;
currentRow[dir > 0 ? 'after' : 'before'](dragObject);
}
function mouseMove(e)
{
if (!dragObject) return false;
e.preventDefault();
checkPageScroll(e);
var mouseY = getMouseY(e);
rowMoving(findDragDirection(mouseY), findDropTargetRow(dragObject, mouseY));
return false;
}
function findDragDirection(y)
{
var yMin = oldY - opts.sensitivity;
var yMax = oldY + opts.sensitivity;
var dir = y >= yMin && y <= yMax ? 0 : y > oldY ? 1 : -1;
if (dir) oldY = y;
return dir;
}
function findDropTargetRow(draggedRow, y)
{
var row, rowY;
var rows = recountRows(table);
var rowsCount = table.data('rowsCount');
for (var i = 0; i < rowsCount; i++)
{
row = rows.eq(i);
rowY = row.offset().top;
if (y >= rowY && y <= rowY + row.outerHeight()) return draggedRow.is(row) ? null : row;
}
return null;
}
function mouseUp(e)
{
if (!dragObject) return null;
e.preventDefault();
$(document).off(eventMove + ' ' + eventEnd);
toggleObjectClass(dragObject);
dragObject = null;
$('html').add('.tablesort-dragging').removeClass('tablesort-dragging');
if (opts.onChange) opts.onChange.call(table);
return false;
}
return table;
});
}
})(jQuery);

View file

@ -24,6 +24,9 @@
<script src="/static/js/comments.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/newcore.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/act.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/selector2.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/selector.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="/static/js/tablesort.js<?php if (NGALLERY['root']['cloudflare-caching'] === true) { echo '?'.time(); } ?>"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
<div class="progress-container fixed-top">

View file

@ -241,6 +241,8 @@ if ($photo->i('id') !== null) {
<a href="#" vote="0" class="vote_btn <?php if (Vote::photo(Auth::userid(), $id) === 0) {
echo 'voted';
} ?>"><span>Мне не&nbsp;нравится</span></a>
<a class="konk_btn" vote="1" href="#" ><span>Красиво, на&nbsp;конкурс!</span></a>
<a href="#" vote="0" class="konk_btn"><span>Неконкурсное фото</span></a>
</div>
<?php } ?>
<div id="votes" class="votes">

View file

@ -20,6 +20,8 @@ $user = new User(Auth::userid());
<table class="tmain">
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Navbar.php'); ?>
<tr>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/blazeface"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" />
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>
@ -39,7 +41,7 @@ $user = new User(Auth::userid());
<script src="https://unpkg.com/leaflet-3d-model/dist/leaflet-3d-model.min.js"></script>
<script>
var pub_pid = 0;
</script>
</script>
<td class="main">
<h1>Предложить медиа на публикацию</h1>
<p>Ваш текущий индекс загрузки: <b><?= $user->i('uploadindex') ?></b></p>
@ -261,9 +263,124 @@ $user = new User(Auth::userid());
JPG, JPEG, PNG, GIF, WEBP, MP4, AVI, 3GP, MKV<br>
Для наибольшей совместимости, ваше видео будет обработано в формат MP4 в кодеке H264
</div>
<div id="preview"></div>
</td>
</tr>
<tr id="tableFaces" style="display: none;">
<td></td>
<td style="padding:2px 15px 5px 2px">
<div id="faceNotify">
</div>
</td>
</tr>
<tr id="tableFaces2" style="display: none;">
<td></td>
<td style="padding:2px 15px 5px 2px">
<br>
<div id="facesCanvas"></div>
<br><br>
<div id="inputFields"></div>
</td>
</tr>
<script>
const imageUpload = document.getElementById('image');
const inputFieldsContainer = document.getElementById('inputFields');
let model;
async function loadModel() {
model = await blazeface.load();
console.log("BlazeFace model loaded");
}
async function detectFaces(image) {
const predictions = await model.estimateFaces(image, false);
return predictions;
}
loadModel();
imageUpload.addEventListener('change', async () => {
const file = imageUpload.files[0];
const img = new Image();
img.src = URL.createObjectURL(file);
img.onload = async () => {
inputFieldsContainer.innerHTML = '';
const predictions = await detectFaces(img);
if (predictions.length > 0) {
const facesTable = document.getElementById('tableFaces');
const facesTable2 = document.getElementById('tableFaces2');
if (facesTable) {
facesTable.removeAttribute('style');
}
if (facesTable2) {
facesTable2.removeAttribute('style');
}
$('#faceNotify').html(' <div style="float:left; border:solid 1px #8C4800; padding:6px 10px 7px; margin-bottom:13px; background-color:#FADD90"><b>Обнаружены лица на фотографии</b><br>Будьте внимательны, фотография будет отклонена, если: <br>· Она нацелена на травлю, буллинг<br>· Ведёт в заблуждение пользователей<br>· Содержит в себе оскорбления<br><br><b>В случае намеренной публикации фотографий, нарушающих правила портала,<br> администрация оставляет за собой право в выдачи ограничений<br> вплоть до полной блокировки аккаунта. Спасибо за понимание</b></div>');
$('#facesCanvas').html('<canvas style="width: 500px;" id="canvas"></canvas>');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
predictions.forEach((prediction, index) => {
const [x, y, width, height] = prediction.topLeft.concat(prediction.bottomRight).flat();
ctx.beginPath();
ctx.rect(x, y, width - x, height - y);
ctx.lineWidth = 3;
ctx.strokeStyle = 'white';
ctx.stroke();
ctx.fillStyle = 'white';
ctx.font = '16px Arial';
ctx.fillText(`Лицо ${index + 1}`, x, y > 10 ? y - 10 : 10);
const inputField = document.createElement('input');
inputField.type = 'text';
inputField.name = `facename_${index + 1}`; // Добавляем динамический name
inputField.placeholder = `Имя участника №${index + 1}`;
inputFieldsContainer.appendChild(inputField);
inputFieldsContainer.appendChild(document.createElement('br'));
});
} else {
$('#faceNotify').html('');
$('#facesCanvas').html('');
}
};
});
</script>
<tr>
<td class="lcol">Дата съёмки:</td>
<td style="padding-bottom:12px">
@ -502,7 +619,7 @@ $user = new User(Auth::userid());
<?php
$galleries = DB::query('SELECT * FROM galleries');
foreach ($galleries as $g) {
echo '<option value="'.$g['id'].'">'.$g['title'].'</option>';
echo '<option value="' . $g['id'] . '">' . $g['title'] . '</option>';
}
?>
</select>
@ -544,7 +661,7 @@ $user = new User(Auth::userid());
<tbody>
<tr>
<td style="padding:0; vertical-align:middle">
<input type="text" name="search_num" id="search_num" maxlength="15" style="width:150px; height:22px" onfocus="showHint('num')" onblur="hideHint('num')"><input type="button" id="searchVehiclesByNumBtn" class="searchVehiclesBtn" style="height:22px; padding-top:0; position:relative; left:-1px" onclick="searchVehicles(0)" value="Найти в БД" disabled="">
<input type="text" name="search_num" id="search_num" maxlength="15" style="width:150px; height:22px" onfocus="showHint('num')" onblur="hideHint('num')"><input type="button" id="searchVehiclesByNumBtn" class="searchVehiclesBtn" style="height:22px; padding-top:0; position:relative; left:-1px" onclick="searchVehicles(0)" value="Найти в БД">
</td>
<td style="padding:0; position:relative; vertical-align:top">
<div style="border: 1px dashed rgb(255, 151, 151); color: red; background-color: rgb(255, 255, 153); padding: 2px 4px 3px; margin: 0px 5px; white-space: nowrap; position: absolute; display: none;" id="num_hint"><small>Введите название модели, или её уникальный ID на сервере <?= NGALLERY['root']['title'] ?>.</small></div>
@ -552,8 +669,141 @@ $user = new User(Auth::userid());
</tr>
</tbody>
</table>
<div style="position:relative; z-index:2000; padding-top:4px"><div id="vlist" class="shadow"></div></div>
</td>
</tr>
<tr>
<td style="padding-top:15px; width:200px" class="lcol">Привязка:</td>
<td style="width:90%; padding:13px 15px 8px 2px">
<div id="views-selector" style="position:absolute; z-index:2000; padding:7px; display:none" class="p20 shadow">
<table id="views">
<tbody><tr>
<td colspan="3" style="text-align:center"><input type="checkbox" name="view_top" value="20" id="v20"> <label for="v20">Вид сверху</label></td>
<td></td>
</tr>
<tr>
<td><input type="radio" name="view_s" value="4" title="Сзади-слева (окна)" class="views-radio-single" style="position:relative; top:7px; left:7px"></td>
<td style="text-align:center">
<input type="radio" name="view_s" value="8" title="Левый борт" class="views-radio-single">
</td>
<td><input type="radio" name="view_s" value="2" title="Спереди-слева (окна)" style="position:relative; top:7px; left:-7px"></td>
<td style="padding:0 35px; line-height:23px" rowspan="3">
<div><input type="radio" name="view_s" value="12" id="v12"> <label for="v12">Заводская табличка</label></div>
<div><input type="radio" name="view_s" value="13" id="v13"> <label for="v13">Отдельные элементы ТС</label></div>
<div class="twoside-old"><input type="radio" name="view_s" value="14" id="v14"> <label for="v14">Не определяется (двухстороннее ТС)</label></div>
<div><input type="radio" name="view_s" value="0" id="v0"> <label for="v0"><span class="s5">&nbsp;Не указан&nbsp;</span></label></div>
<div class="sm" style="margin-top:15px"><a href="#" class="views-toggle-link dot">Переключить на: <span class="twoside-single">Одностороннее ТС</span><span class="twoside-twoside">Двухстороннее ТС</span></a></div>
</td>
</tr>
<tr>
<td style="padding:0 2px"><input type="radio" name="view_s" value="7" title="Вид строго сзади" class="views-radio-single"></td>
<td class="views-image">
<table style="width:138px; height:82px">
<tbody><tr>
<td style="text-align:left; padding-left:25px">
<input type="radio" name="view_s" value="9" title="Салон, вид вперёд">
</td>
<td style="text-align:right; padding:0">
<input type="radio" name="view_s" value="10" title="Салон, вид назад" class="views-radio-single">
<input type="radio" name="view_s" value="11" title="Кабина" style="position:relative; top:-7px">
</td>
</tr>
</tbody></table>
</td>
<td style="padding:0 2px"><input type="radio" name="view_s" value="5" title="Вид строго спереди"></td>
</tr>
<tr>
<td><input type="radio" name="view_s" value="3" title="Сзади-справа (двери)" class="views-radio-single" style="position:relative; top:-7px; left:7px"></td>
<td style="text-align:center">
<input type="radio" name="view_s" value="6" title="Правый борт">
</td>
<td><input type="radio" name="view_s" value="1" title="Спереди-справа (двери)" style="position:relative; top:-7px; left:-7px"></td>
</tr>
<tr>
<td colspan="3" style="text-align:center"><input type="checkbox" name="view_bottom" value="40" id="v40"> <label for="v40">Вид снизу</label></td>
<td></td>
</tr>
</tbody></table>
<script>
function openViewSelector(val, el, twoside)
{
var selector = $('#views-selector');
var view = val % 20;
var modifier = val - view;
$('input[value="' + view + '"]', selector).prop('checked', true);
$('#v20').prop('checked', modifier == 20);
$('#v40').prop('checked', modifier == 40);
if (view != 14)
{
selector.attr('data-twoside', twoside);
$('.twoside-old').hide();
}
else
{
selector.attr('data-twoside', 1);
$('.twoside-old').show();
}
var p = el.offset();
selector.css('left', p.left + 'px').css('top', (p.top + el.height() + 3) + 'px').show();
}
function setViewSelectorCallback(func)
{
var selector = $('#views-selector');
$('input[type="radio"]', selector).on('click', function(e)
{
var view = parseInt($('input[type="radio"]:checked', selector).val());
var modifier = parseInt($('input[type="checkbox"]:checked', selector).val());
if (isNaN(modifier)) modifier = 0;
var label = view || !modifier ? views[view] : '';
if (label != '' && modifier) label += ' + ';
if (modifier) label += views[modifier];
func(e, view, modifier, label);
selector.hide();
});
$('input[type="checkbox"]', selector).on('click', function()
{
if ($(this).is('#v20:checked')) $('#v40').prop('checked', false); else
if ($(this).is('#v40:checked')) $('#v20').prop('checked', false);
});
}
$(document).ready(function()
{
$('.views-toggle-link').on('click', function()
{
var selector = $('#views-selector');
var twoside = selector.attr('data-twoside');
selector.attr('data-twoside', twoside == 1 ? 0 : 1);
return false;
});
});
</script>
</div>
<div class="no-links" style="padding-top:2px; margin-bottom:7px"><i>Фотография ни к чему не привязана.</i></div>
<div id="links">
<table id="conn_veh" style="margin-bottom:7px; display:none">
</table>
<table id="conn_gid" style="margin-bottom:7px; display:none">
</table>
</div>
</td>
</tr>

View file

@ -4,7 +4,11 @@ use \App\Services\{Auth, DB, Date};
use \App\Models\Vehicle;
$id = explode('/', $_SERVER['REQUEST_URI'])[2];
$vehicle = new Vehicle($id);
$data = DB::query('SELECT * FROM entities_data WHERE id=:id', array(':id' => $id))[0];
$vehicle = new Vehicle($data['entityid']);
$vehicledatavariables = json_decode($data['content'], true);
?>
@ -12,7 +16,7 @@ $vehicle = new Vehicle($id);
<html lang="ru">
<head>
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/LoadHead.php'); ?>
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/LoadHead.php'); ?>
</head>
@ -24,15 +28,17 @@ $vehicle = new Vehicle($id);
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Navbar.php'); ?>
<tr>
<td class="main">
<h1>Пиксельск, бутылка pepsi 001</h1>
<h1><?= $data['title'] ?></h1>
<table class="horlines">
<col width="150">
<col>
<?php
$vehiclevariables = json_decode($vehicle->getvehicle('sampledata'), true);
$num = 1;
foreach ($vehiclevariables as $vb) {
echo '<tr class="h21"><td class="ds nw">'.$vb['name'].':</td><td class="d"><b>1975</b></td></tr>';
echo '<tr class="h21"><td class="ds nw">' . $vb['name'] . ':</td><td class="d"><b>' . $vehicledatavariables[$num]['value'] . '</b></td></tr>';
$num++;
}
?>
@ -42,21 +48,10 @@ $vehicle = new Vehicle($id);
<tr>
<td class="footer"><b><a href="/">Главная</a> &nbsp; &nbsp; <a href="/lk/">Личный кабинет</a> &nbsp; &nbsp; <a href="https://forum.transphoto.org">Форум</a> &nbsp; &nbsp; <a href="/rules/">Правила</a> &nbsp; &nbsp; <a href="/admin/">Редколлегия</a></b><br>
<a href="/set.php?dark=0" style="display:inline-block; padding:1px 10px; margin-top:5px; background-color:#ddd; color:#333">Светлая тема</a>
<div class="sitecopy">&copy; Администрация ТрансФото и авторы материалов, 2002—2024<br>Использование фотографий и иных материалов, опубликованных на сайте, допускается только с разрешения их авторов.</div>
<div style="margin:15px 0">
<noindex>
<!-- Yandex.Metrika informer -->
<a href="https://metrika.yandex.ru/stat/?id=73971775&amp;from=informer" target="_blank" rel="nofollow"><img src="https://informer.yandex.ru/informer/73971775/3_0_DDDDDDFF_DDDDDDFF_0_pageviews"
style="width:88px; height:31px; border:0;" alt="Яндекс.Метрика" title="Яндекс.Метрика: данные за сегодня (просмотры, визиты и уникальные посетители)" class="ym-advanced-informer" data-cid="73971775" data-lang="ru" /></a>
<!-- /Yandex.Metrika informer -->
<?php include($_SERVER['DOCUMENT_ROOT'] . '/views/components/Footer.php'); ?>
</noindex>
</div>
</td>
</tr>
</table>

View file

@ -1,6 +1,90 @@
<?php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Face Detection with TensorFlow.js</title>
</head>
<body>
<h1>Face Detection using TensorFlow.js</h1>
<input type="file" id="imageUpload" accept="image/*">
<br><br>
<canvas style="width: 100px;" id="canvas"></canvas>
<br><br>
<div id="inputFields"></div> <!-- Контейнер для динамически создаваемых полей ввода -->
<br>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/blazeface"></script>
<script>
// app.js
const imageUpload = document.getElementById('imageUpload');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const inputFieldsContainer = document.getElementById('inputFields');
$videoFile = $_SERVER['DOCUMENT_ROOT'].'/static/2.mp4';
$ffmpegPath = 'E:\Maksim\kandle\app\Controllers\Video\Exec\ffmpeg.exe';
$output = exec($ffmpegPath. ' -i' .$vid, $outputt);
var_dump($output);
// Переменная для хранения модели
let model;
// Функция для загрузки модели
async function loadModel() {
model = await blazeface.load();
console.log("BlazeFace model loaded");
}
// Функция для распознавания лиц
async function detectFaces(image) {
const predictions = await model.estimateFaces(image, false);
return predictions;
}
// Загрузка модели при инициализации
loadModel();
imageUpload.addEventListener('change', async () => {
const file = imageUpload.files[0];
const img = new Image();
img.src = URL.createObjectURL(file);
img.onload = async () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
// Очищаем предыдущие поля ввода
inputFieldsContainer.innerHTML = '';
// Используем кэшированную модель для распознавания
const predictions = await detectFaces(img);
if (predictions.length > 0) {
predictions.forEach((prediction, index) => {
// Получаем координаты лица
const [x, y, width, height] = prediction.topLeft.concat(prediction.bottomRight).flat();
// Рисуем рамку вокруг лица
ctx.beginPath();
ctx.rect(x, y, width - x, height - y);
ctx.lineWidth = 3;
ctx.strokeStyle = 'red';
ctx.stroke();
// Добавляем номер лица на изображении
ctx.fillStyle = 'red';
ctx.font = '16px Arial';
ctx.fillText(`Лицо ${index + 1}`, x, y > 10 ? y - 10 : 10);
// Создаем поле ввода для каждого распознанного лица
const inputField = document.createElement('input');
inputField.type = 'text';
inputField.placeholder = `Информация о лице ${index + 1}`;
inputFieldsContainer.appendChild(inputField);
inputFieldsContainer.appendChild(document.createElement('br'));
});
} else {
alert('No faces detected');
}
};
});
</script>
</body>
</html>