feat: add videodb media index with Docker stack
- Add videodb PHP/MySQL media collection manager (Blu-ray, DVD, CD) - Dockerfile: PHP 8.1 + Apache with GD/mysqli/exif extensions - docker-compose.yml: app on port 6761 + MySQL 8.0 with health checks - docker-entrypoint.sh: auto-generates config.inc.php from env vars, waits for MySQL, initializes DB schema idempotently - init-db.php: CLI schema installer using app's own prefix_query() logic - Persistent volumes for DB, cache, and cover images Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
42
videodb/templates/elegant/js/Autocompleter.js
Normal file
42
videodb/templates/elegant/js/Autocompleter.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Enhanced Ajax.Autocompleter with support for parameters array and custom completion code
|
||||
*/
|
||||
Ajax.Autocompleter2 = Class.create(Autocompleter.Base, {
|
||||
initialize: function(element, update, url, options) {
|
||||
this.baseInitialize(element, update, options);
|
||||
this.options.asynchronous = true;
|
||||
this.options.onComplete = this.onComplete.bind(this);
|
||||
this.options.defaultParams = this.options.parameters || null;
|
||||
this.url = url;
|
||||
},
|
||||
|
||||
// allow parameters array
|
||||
getUpdatedChoices: function() {
|
||||
this.startIndicator();
|
||||
|
||||
var entry = encodeURIComponent(this.options.paramName) + '=' +
|
||||
encodeURIComponent(this.getToken());
|
||||
|
||||
this.options.parameters = this.options.callback ?
|
||||
this.options.callback(this.element, entry) : entry;
|
||||
|
||||
if(this.options.defaultParams) {
|
||||
// if defaultParams is a string, keep as is and don't convert
|
||||
var defaultParams = Object.isString(this.options.defaultParams) ? this.options.defaultParams : Object.toQueryString(this.options.defaultParams);
|
||||
|
||||
this.options.parameters += '&' + defaultParams;
|
||||
}
|
||||
|
||||
new Ajax.Request(this.url, this.options);
|
||||
},
|
||||
|
||||
// allow custom completion code
|
||||
onComplete: function(request) {
|
||||
if (this.options.updateContent) {
|
||||
this.options.updateContent(request);
|
||||
this.stopIndicator();
|
||||
}
|
||||
else this.updateChoices(request.responseText);
|
||||
}
|
||||
});
|
||||
|
||||
110
videodb/templates/elegant/js/edit.js
Normal file
110
videodb/templates/elegant/js/edit.js
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Prototype javascript effects for editing
|
||||
*
|
||||
* @package Templates
|
||||
*
|
||||
* @todo Check if Ajax.Responder could be used
|
||||
*
|
||||
* @author Andreas Goetz <cpuidle@gmx.de>
|
||||
* $Id: edit.js,v 1.10 2009/03/26 22:23:49 andig2 Exp $
|
||||
*/
|
||||
|
||||
/**
|
||||
* Using global responder to manage indicator handling for multiple requests
|
||||
*/
|
||||
Ajax.Responders.register({
|
||||
onCreate: function() {
|
||||
if (Ajax.activeRequestCount > 0 && $('indicator1')) {
|
||||
$('indicator1').setStyle({display: 'inline'});
|
||||
}
|
||||
},
|
||||
|
||||
onComplete: function() {
|
||||
if(Ajax.activeRequestCount == 0 && $('indicator1')) {
|
||||
$('indicator1').setStyle({display: 'none'});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Title search
|
||||
*/
|
||||
function bindTitle()
|
||||
{
|
||||
$('title').focus();
|
||||
|
||||
// autocompleter is only enabled for new items
|
||||
if (!$F('imdbID'))
|
||||
{
|
||||
new Ajax.Autocompleter("title", "title_choices", "edit.php", {paramName: "ajax_autocomplete_title",
|
||||
minChars: 3, frequency: 0.8,
|
||||
|
||||
afterUpdateElement: function(text, li) {
|
||||
document.edi.imdbID.value = li.id;
|
||||
document.edi.lookup1.checked= true;
|
||||
|
||||
// start image search
|
||||
bindImageLookup();
|
||||
|
||||
new Ajax.Request('edit.php', {
|
||||
parameters: {ajax_prefetch_id: li.id},
|
||||
/*
|
||||
// inline update
|
||||
onSuccess: function(transport) {
|
||||
$('indicator1').setStyle({display: 'none'});
|
||||
|
||||
var el = $('u1');
|
||||
el.update(transport.responseText);
|
||||
// alert(el.down('div#content',0).innerHTML);
|
||||
$('content').update(el.down('#content',0).innerHTML);
|
||||
el.update('');
|
||||
}
|
||||
*/
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rating
|
||||
*/
|
||||
function bindRating(control, control_val, val)
|
||||
{
|
||||
var rating = new Control.Rating(control, {updateUrl: false, value: val,
|
||||
rated: false, multiple: true, max:10, capture: true,
|
||||
|
||||
afterChange: function(value) {
|
||||
$('rating').value = value;
|
||||
$(control_val).update(value);
|
||||
rating.options.rated = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Image lookup
|
||||
*/
|
||||
function bindImageLookup()
|
||||
{
|
||||
if (!$F('title')) return;
|
||||
|
||||
new Ajax.Request('lookup.php', {
|
||||
parameters: {ajax_render: 1, searchtype: 'image', engine: 'google', find: $F('title')},
|
||||
|
||||
onSuccess: function(transport, json) {
|
||||
if (json.count > 0) {
|
||||
$('imagecontent').update(transport.responseText);
|
||||
|
||||
// either img or a
|
||||
$$('#imagecontent div.thumbnail').invoke('observe', 'click', function(event) {
|
||||
$('imgurl').value = event.element().readAttribute('url');
|
||||
Event.stop(event);
|
||||
});
|
||||
|
||||
$('images').setStyle({display: 'block'});
|
||||
// Effect.BlindDown('images');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
160
videodb/templates/elegant/js/index.js
Normal file
160
videodb/templates/elegant/js/index.js
Normal file
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Prototype javascript effects for filtering
|
||||
*
|
||||
* @package Templates
|
||||
* @author Andreas Goetz <cpuidle@gmx.de>
|
||||
* $Id: index.js,v 1.10 2009/03/26 11:17:21 andig2 Exp $
|
||||
*/
|
||||
|
||||
/**
|
||||
* Using global responder to manage indicator handling for multiple requests
|
||||
*/
|
||||
Ajax.Responders.register({
|
||||
onCreate: function() {
|
||||
if (Ajax.activeRequestCount > 0 && $('indicator1')) {
|
||||
$('indicator1').setStyle({display: 'inline'});
|
||||
}
|
||||
},
|
||||
|
||||
onComplete: function() {
|
||||
if(Ajax.activeRequestCount == 0 && $('indicator1')) {
|
||||
$('indicator1').setStyle({display: 'none'});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Clean pre-populated text from input controls
|
||||
*/
|
||||
function clearInput(id, term_to_clear)
|
||||
{
|
||||
el = $(id);
|
||||
if (el) {
|
||||
// Clear input if it matches default value
|
||||
if (el.value == term_to_clear) el.value = '';
|
||||
// If the value is blank, then put back term
|
||||
else if (el.value == '') el.value = term_to_clear;
|
||||
}
|
||||
}
|
||||
|
||||
Event.observe(document, 'dom:loaded', function()
|
||||
{
|
||||
/**
|
||||
* More/less columns
|
||||
*/
|
||||
function send_request(event, val) {
|
||||
$('columns_less').setAttribute('listcolumns', val);
|
||||
|
||||
new Ajax.Request('index.php', {
|
||||
parameters: {ajax_render: 1, listcolumns: val},
|
||||
|
||||
onSuccess: function(transport) {
|
||||
$('content').update(transport.responseText);
|
||||
}
|
||||
});
|
||||
|
||||
Event.stop(event);
|
||||
}
|
||||
|
||||
$('columns_less').observe('click', function(event) {
|
||||
var val = $('columns_less').getAttribute('listcolumns');
|
||||
val = (val > 1) ? val-1 : 1;
|
||||
|
||||
send_request(event, val);
|
||||
});
|
||||
|
||||
$('columns_more').observe('click', function(event) {
|
||||
var val = $('columns_less').getAttribute('listcolumns');
|
||||
val++;
|
||||
|
||||
send_request(event, val);
|
||||
});
|
||||
|
||||
/**
|
||||
* Render list contents and update item count
|
||||
*/
|
||||
function render_list(transport, json) {
|
||||
$('content').update(transport.responseText);
|
||||
$('count').update(json.totalresults);
|
||||
|
||||
// paged display
|
||||
if (json.maxpageno) {
|
||||
$('pageno').update(json.pageno);
|
||||
$('maxpageno').update(json.maxpageno);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters
|
||||
*/
|
||||
$('showtv').observe('click', function(event) {
|
||||
new Ajax.Request('index.php', {
|
||||
parameters: {ajax_render: 1, showtv: ($('showtv').checked) ? 1 : 0},
|
||||
onSuccess: render_list
|
||||
});
|
||||
});
|
||||
|
||||
// radio buttons
|
||||
$$('#filtersalphabet input[type="radio"]').invoke('observe', 'click', function(event) {
|
||||
new Ajax.Request('index.php', {
|
||||
parameters: {ajax_render: 1, filter: event.element().value},
|
||||
onSuccess: render_list
|
||||
});
|
||||
});
|
||||
/*
|
||||
// seen links
|
||||
$$('div.list_seen a').invoke('observe', 'click', function(event) {
|
||||
Event.stop(event);
|
||||
|
||||
$('filter'+event.element().readAttribute('filter')).click();
|
||||
});
|
||||
*/
|
||||
|
||||
/**
|
||||
* Owner selection
|
||||
*/
|
||||
$$('#owner').invoke('observe', 'change', function(event) {
|
||||
new Ajax.Request('index.php', {
|
||||
parameters: {ajax_render: 1, owner: $F('owner')},
|
||||
onSuccess: render_list
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Media selection
|
||||
*/
|
||||
$$('#mediafilter').invoke('observe', 'change', function(event) {
|
||||
new Ajax.Request('index.php', {
|
||||
parameters: {ajax_render: 1, mediafilter: $F('mediafilter')},
|
||||
onSuccess: render_list
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Order selection
|
||||
*/
|
||||
$$('#order').invoke('observe', 'change', function(event) {
|
||||
new Ajax.Request('index.php', {
|
||||
parameters: {ajax_render: 1, order: $F('order')},
|
||||
onSuccess: render_list
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Quicksearch
|
||||
*/
|
||||
$('quicksearch').focus();
|
||||
|
||||
new Ajax.Autocompleter("quicksearch", "item_choices", "index.php", {paramName: "ajax_quicksearch",
|
||||
minChars: 1, frequency: 0.2,
|
||||
afterUpdateElement: function(text, li) {
|
||||
document.location = 'show.php?id='+li.id;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Enable disabled controls
|
||||
*/
|
||||
// $$('.autoenable').invoke('writeAttribute', {disabled: false});
|
||||
// $('quicksearch').writeAttribute({disabled: false});
|
||||
});
|
||||
94
videodb/templates/elegant/js/search.js
Normal file
94
videodb/templates/elegant/js/search.js
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Prototype javascript effects for searching
|
||||
*
|
||||
* @package Templates
|
||||
* @author Andreas Goetz <cpuidle@gmx.de>
|
||||
* $Id: search.js,v 1.8 2009/04/04 16:26:30 andig2 Exp $
|
||||
*/
|
||||
|
||||
Event.observe(document, 'dom:loaded', function()
|
||||
{
|
||||
/**
|
||||
* Quicksearch
|
||||
*/
|
||||
$('q').focus();
|
||||
|
||||
// Ajax.Autocompleter2 allows passing in complex search parameters like arrays
|
||||
new Ajax.Autocompleter("q", "item_choices", "search.php", {
|
||||
minChars: 2, indicator: 'indicator1', frequency: 0.2,
|
||||
parameters: collect_parameters({ajax_quicksearch: 1}),
|
||||
|
||||
afterUpdateElement: function(text, li) {
|
||||
document.location = 'show.php?id=' + li.id;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Collect form parameters for search fields and genres
|
||||
*/
|
||||
function collect_parameters(parameters)
|
||||
{
|
||||
var res = Object.toQueryString(parameters);
|
||||
|
||||
// owner
|
||||
if ($('owner')) {
|
||||
res += '&owner=' + $F('owner');
|
||||
}
|
||||
|
||||
// genres
|
||||
$$('#genres input').each(function(el) {
|
||||
if (el.checked) {
|
||||
res += '&genres[]=' + el.value;
|
||||
}
|
||||
});
|
||||
|
||||
// fields
|
||||
$$('#fields option').each(function(el) {
|
||||
if (el.selected) {
|
||||
res += '&fields[]=' + el.value;
|
||||
}
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create submit request
|
||||
*/
|
||||
function submit_form(event)
|
||||
{
|
||||
// if (!$F('q')) return;
|
||||
$('indicator1').setStyle({display: 'inline'});
|
||||
|
||||
new Ajax.Request('search.php', {
|
||||
parameters: collect_parameters({ajax_render: 1, q: $F('q')}),
|
||||
|
||||
onSuccess: function(transport, json) {
|
||||
$('indicator1').setStyle({display: 'none'});
|
||||
$('content').update(transport.responseText);
|
||||
$('count').update(json.count);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search fields
|
||||
*/
|
||||
$$('#fields option').invoke('observe', 'click', submit_form);
|
||||
|
||||
// toggle all
|
||||
$('select_all').observe('click', function() {
|
||||
selectAllFields();
|
||||
submit_form();
|
||||
});
|
||||
|
||||
/**
|
||||
* Genres
|
||||
*/
|
||||
$$('#genres input').invoke('observe', 'change', submit_form);
|
||||
|
||||
/**
|
||||
* Owner selection
|
||||
*/
|
||||
$$('#owner').invoke('observe', 'change', submit_form);
|
||||
});
|
||||
152
videodb/templates/elegant/js/show.js
Normal file
152
videodb/templates/elegant/js/show.js
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Prototype javascript effects for showing
|
||||
*
|
||||
* @package Templates
|
||||
* @author Andreas Goetz <cpuidle@gmx.de>
|
||||
* $Id: show.js,v 1.5 2009/04/04 16:26:30 andig2 Exp $
|
||||
*/
|
||||
|
||||
/**
|
||||
* Toggle element's visibility
|
||||
*/
|
||||
function toggler(el)
|
||||
{
|
||||
if ($(el).style.display == 'block')
|
||||
$(el).setStyle({display: 'none'});
|
||||
else
|
||||
$(el).setStyle({display: 'block'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Using global responder to manage indicator handling for multiple requests
|
||||
*/
|
||||
Ajax.Responders.register({
|
||||
onCreate: function() {
|
||||
if (Ajax.activeRequestCount > 0 && $('indicator1')) {
|
||||
$('indicator1').setStyle({display: 'inline'});
|
||||
}
|
||||
},
|
||||
|
||||
onComplete: function() {
|
||||
if(Ajax.activeRequestCount == 0 && $('indicator1')) {
|
||||
$('indicator1').setStyle({display: 'none'});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Seen
|
||||
*/
|
||||
function bindSeen(id)
|
||||
{
|
||||
$('seen').observe('click', function(event) {
|
||||
|
||||
new Ajax.Request('show.php', {
|
||||
parameters: {ajax_update: id, seen: ($('seen').checked) ? 1 : 0},
|
||||
|
||||
onSuccess: function(transport, json) {
|
||||
$('seen').checked = json.result;
|
||||
$('seen_label').setStyle({display: (json.result) ? 'inline' : 'none'});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rating
|
||||
*/
|
||||
function bindRating(id, url, control, control_val, val, editable)
|
||||
{
|
||||
var rating = new Control.Rating(control, {updateUrl: url, updateParameterName: 'rating', value: val,
|
||||
rated: false, multiple: true, max:10, capture: true,
|
||||
parameters: {ajax_update: id},
|
||||
|
||||
afterChange: function(value) {
|
||||
$(control_val).update(value);
|
||||
this.options.rated = false;
|
||||
}.bind(this)
|
||||
});
|
||||
|
||||
if (!editable) rating.disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Youtube
|
||||
*/
|
||||
function bindYoutube(title)
|
||||
{
|
||||
new Ajax.Request('lookup.php', {
|
||||
parameters: {ajax_render: 1, searchtype: 'trailer', engine: 'youtube', find: title},
|
||||
|
||||
onSuccess: function(transport, json) {
|
||||
if (json.count > 0) {
|
||||
$('youtube').setStyle({display: 'inline'});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Purchase
|
||||
*/
|
||||
function bindPurchase(title, engines)
|
||||
{
|
||||
engines.each(function(e)
|
||||
{
|
||||
new Ajax.Request('lookup.php', {
|
||||
parameters: {ajax_render: 1, searchtype: 'purchase', engine: e, find: title},
|
||||
|
||||
onSuccess: function(transport, json) {
|
||||
if (json.count > 0) {
|
||||
$('purchase').setStyle({display: 'inline'});
|
||||
$('purchasecontent').insert(transport.responseText);
|
||||
// $('images').setStyle({display: 'block'});
|
||||
// Effect.BlindDown('images');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Torrent
|
||||
*/
|
||||
function bindTorrent(title, engines)
|
||||
{
|
||||
engines.each(function(e)
|
||||
{
|
||||
new Ajax.Request('lookup.php', {
|
||||
parameters: {ajax_render: 1, searchtype: 'download', engine: e, find: title},
|
||||
|
||||
onSuccess: function(transport, json) {
|
||||
if (json.count > 0) {
|
||||
$('torrent').setStyle({display: 'inline'});
|
||||
$('torrentcontent').insert(transport.responseText);
|
||||
// $('images').setStyle({display: 'block'});
|
||||
// Effect.BlindDown('images');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function bindRenderer(title, type, engines)
|
||||
{
|
||||
engines.each(function(e)
|
||||
{
|
||||
new Ajax.Request('lookup.php', {
|
||||
parameters: {ajax_render: 1, searchtype: type, engine: e, find: title},
|
||||
|
||||
onSuccess: function(transport, json) {
|
||||
//alert(this.options);
|
||||
if (json.count > 0) {
|
||||
$('purchase').setStyle({display: 'inline'});
|
||||
$('purchasecontent').insert(transport.responseText);
|
||||
// $('images').setStyle({display: 'block'});
|
||||
// Effect.BlindDown('images');
|
||||
}
|
||||
}.bind(this)
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user