chapter 5: Implement sliders and filters

The application communicates back and forth with JavaScript using ports
and subscriptions. It also receives initial data from JavaScript by
passing 'flags' to the init function.
This commit is contained in:
Alexander Kobjolke 2023-12-14 11:22:01 +01:00
parent 5f80082567
commit 43971bd268
6 changed files with 3249 additions and 260 deletions

730
app.js
View file

@ -80,6 +80,190 @@ function A9(fun, a, b, c, d, e, f, g, h, i) {
console.warn('Compiled in DEV mode. Follow the advice at https://elm-lang.org/0.19.1/optimize for better performance and smaller assets.'); console.warn('Compiled in DEV mode. Follow the advice at https://elm-lang.org/0.19.1/optimize for better performance and smaller assets.');
// EQUALITY
function _Utils_eq(x, y)
{
for (
var pair, stack = [], isEqual = _Utils_eqHelp(x, y, 0, stack);
isEqual && (pair = stack.pop());
isEqual = _Utils_eqHelp(pair.a, pair.b, 0, stack)
)
{}
return isEqual;
}
function _Utils_eqHelp(x, y, depth, stack)
{
if (x === y)
{
return true;
}
if (typeof x !== 'object' || x === null || y === null)
{
typeof x === 'function' && _Debug_crash(5);
return false;
}
if (depth > 100)
{
stack.push(_Utils_Tuple2(x,y));
return true;
}
/**/
if (x.$ === 'Set_elm_builtin')
{
x = $elm$core$Set$toList(x);
y = $elm$core$Set$toList(y);
}
if (x.$ === 'RBNode_elm_builtin' || x.$ === 'RBEmpty_elm_builtin')
{
x = $elm$core$Dict$toList(x);
y = $elm$core$Dict$toList(y);
}
//*/
/**_UNUSED/
if (x.$ < 0)
{
x = $elm$core$Dict$toList(x);
y = $elm$core$Dict$toList(y);
}
//*/
for (var key in x)
{
if (!_Utils_eqHelp(x[key], y[key], depth + 1, stack))
{
return false;
}
}
return true;
}
var _Utils_equal = F2(_Utils_eq);
var _Utils_notEqual = F2(function(a, b) { return !_Utils_eq(a,b); });
// COMPARISONS
// Code in Generate/JavaScript.hs, Basics.js, and List.js depends on
// the particular integer values assigned to LT, EQ, and GT.
function _Utils_cmp(x, y, ord)
{
if (typeof x !== 'object')
{
return x === y ? /*EQ*/ 0 : x < y ? /*LT*/ -1 : /*GT*/ 1;
}
/**/
if (x instanceof String)
{
var a = x.valueOf();
var b = y.valueOf();
return a === b ? 0 : a < b ? -1 : 1;
}
//*/
/**_UNUSED/
if (typeof x.$ === 'undefined')
//*/
/**/
if (x.$[0] === '#')
//*/
{
return (ord = _Utils_cmp(x.a, y.a))
? ord
: (ord = _Utils_cmp(x.b, y.b))
? ord
: _Utils_cmp(x.c, y.c);
}
// traverse conses until end of a list or a mismatch
for (; x.b && y.b && !(ord = _Utils_cmp(x.a, y.a)); x = x.b, y = y.b) {} // WHILE_CONSES
return ord || (x.b ? /*GT*/ 1 : y.b ? /*LT*/ -1 : /*EQ*/ 0);
}
var _Utils_lt = F2(function(a, b) { return _Utils_cmp(a, b) < 0; });
var _Utils_le = F2(function(a, b) { return _Utils_cmp(a, b) < 1; });
var _Utils_gt = F2(function(a, b) { return _Utils_cmp(a, b) > 0; });
var _Utils_ge = F2(function(a, b) { return _Utils_cmp(a, b) >= 0; });
var _Utils_compare = F2(function(x, y)
{
var n = _Utils_cmp(x, y);
return n < 0 ? $elm$core$Basics$LT : n ? $elm$core$Basics$GT : $elm$core$Basics$EQ;
});
// COMMON VALUES
var _Utils_Tuple0_UNUSED = 0;
var _Utils_Tuple0 = { $: '#0' };
function _Utils_Tuple2_UNUSED(a, b) { return { a: a, b: b }; }
function _Utils_Tuple2(a, b) { return { $: '#2', a: a, b: b }; }
function _Utils_Tuple3_UNUSED(a, b, c) { return { a: a, b: b, c: c }; }
function _Utils_Tuple3(a, b, c) { return { $: '#3', a: a, b: b, c: c }; }
function _Utils_chr_UNUSED(c) { return c; }
function _Utils_chr(c) { return new String(c); }
// RECORDS
function _Utils_update(oldRecord, updatedFields)
{
var newRecord = {};
for (var key in oldRecord)
{
newRecord[key] = oldRecord[key];
}
for (var key in updatedFields)
{
newRecord[key] = updatedFields[key];
}
return newRecord;
}
// APPEND
var _Utils_append = F2(_Utils_ap);
function _Utils_ap(xs, ys)
{
// append Strings
if (typeof xs === 'string')
{
return xs + ys;
}
// append Lists
if (!xs.b)
{
return ys;
}
var root = _List_Cons(xs.a, ys);
xs = xs.b
for (var curr = root; xs.b; xs = xs.b) // WHILE_CONS
{
curr = curr.b = _List_Cons(xs.a, ys);
}
return root;
}
var _List_Nil_UNUSED = { $: 0 }; var _List_Nil_UNUSED = { $: 0 };
var _List_Nil = { $: '[]' }; var _List_Nil = { $: '[]' };
@ -609,190 +793,6 @@ function _Debug_regionToString(region)
// EQUALITY
function _Utils_eq(x, y)
{
for (
var pair, stack = [], isEqual = _Utils_eqHelp(x, y, 0, stack);
isEqual && (pair = stack.pop());
isEqual = _Utils_eqHelp(pair.a, pair.b, 0, stack)
)
{}
return isEqual;
}
function _Utils_eqHelp(x, y, depth, stack)
{
if (x === y)
{
return true;
}
if (typeof x !== 'object' || x === null || y === null)
{
typeof x === 'function' && _Debug_crash(5);
return false;
}
if (depth > 100)
{
stack.push(_Utils_Tuple2(x,y));
return true;
}
/**/
if (x.$ === 'Set_elm_builtin')
{
x = $elm$core$Set$toList(x);
y = $elm$core$Set$toList(y);
}
if (x.$ === 'RBNode_elm_builtin' || x.$ === 'RBEmpty_elm_builtin')
{
x = $elm$core$Dict$toList(x);
y = $elm$core$Dict$toList(y);
}
//*/
/**_UNUSED/
if (x.$ < 0)
{
x = $elm$core$Dict$toList(x);
y = $elm$core$Dict$toList(y);
}
//*/
for (var key in x)
{
if (!_Utils_eqHelp(x[key], y[key], depth + 1, stack))
{
return false;
}
}
return true;
}
var _Utils_equal = F2(_Utils_eq);
var _Utils_notEqual = F2(function(a, b) { return !_Utils_eq(a,b); });
// COMPARISONS
// Code in Generate/JavaScript.hs, Basics.js, and List.js depends on
// the particular integer values assigned to LT, EQ, and GT.
function _Utils_cmp(x, y, ord)
{
if (typeof x !== 'object')
{
return x === y ? /*EQ*/ 0 : x < y ? /*LT*/ -1 : /*GT*/ 1;
}
/**/
if (x instanceof String)
{
var a = x.valueOf();
var b = y.valueOf();
return a === b ? 0 : a < b ? -1 : 1;
}
//*/
/**_UNUSED/
if (typeof x.$ === 'undefined')
//*/
/**/
if (x.$[0] === '#')
//*/
{
return (ord = _Utils_cmp(x.a, y.a))
? ord
: (ord = _Utils_cmp(x.b, y.b))
? ord
: _Utils_cmp(x.c, y.c);
}
// traverse conses until end of a list or a mismatch
for (; x.b && y.b && !(ord = _Utils_cmp(x.a, y.a)); x = x.b, y = y.b) {} // WHILE_CONSES
return ord || (x.b ? /*GT*/ 1 : y.b ? /*LT*/ -1 : /*EQ*/ 0);
}
var _Utils_lt = F2(function(a, b) { return _Utils_cmp(a, b) < 0; });
var _Utils_le = F2(function(a, b) { return _Utils_cmp(a, b) < 1; });
var _Utils_gt = F2(function(a, b) { return _Utils_cmp(a, b) > 0; });
var _Utils_ge = F2(function(a, b) { return _Utils_cmp(a, b) >= 0; });
var _Utils_compare = F2(function(x, y)
{
var n = _Utils_cmp(x, y);
return n < 0 ? $elm$core$Basics$LT : n ? $elm$core$Basics$GT : $elm$core$Basics$EQ;
});
// COMMON VALUES
var _Utils_Tuple0_UNUSED = 0;
var _Utils_Tuple0 = { $: '#0' };
function _Utils_Tuple2_UNUSED(a, b) { return { a: a, b: b }; }
function _Utils_Tuple2(a, b) { return { $: '#2', a: a, b: b }; }
function _Utils_Tuple3_UNUSED(a, b, c) { return { a: a, b: b, c: c }; }
function _Utils_Tuple3(a, b, c) { return { $: '#3', a: a, b: b, c: c }; }
function _Utils_chr_UNUSED(c) { return c; }
function _Utils_chr(c) { return new String(c); }
// RECORDS
function _Utils_update(oldRecord, updatedFields)
{
var newRecord = {};
for (var key in oldRecord)
{
newRecord[key] = oldRecord[key];
}
for (var key in updatedFields)
{
newRecord[key] = updatedFields[key];
}
return newRecord;
}
// APPEND
var _Utils_append = F2(_Utils_ap);
function _Utils_ap(xs, ys)
{
// append Strings
if (typeof xs === 'string')
{
return xs + ys;
}
// append Lists
if (!xs.b)
{
return ys;
}
var root = _List_Cons(xs.a, ys);
xs = xs.b
for (var curr = root; xs.b; xs = xs.b) // WHILE_CONS
{
curr = curr.b = _List_Cons(xs.a, ys);
}
return root;
}
// MATH // MATH
var _Basics_add = F2(function(a, b) { return a + b; }); var _Basics_add = F2(function(a, b) { return a + b; });
@ -4629,32 +4629,9 @@ function _Time_getZoneName()
}); });
} }
var $elm$core$Basics$EQ = {$: 'EQ'}; var $elm$core$Basics$EQ = {$: 'EQ'};
var $elm$core$Basics$GT = {$: 'GT'};
var $elm$core$Basics$LT = {$: 'LT'}; var $elm$core$Basics$LT = {$: 'LT'};
var $elm$core$List$cons = _List_cons; var $elm$core$List$cons = _List_cons;
var $elm$core$Elm$JsArray$foldr = _JsArray_foldr;
var $elm$core$Array$foldr = F3(
function (func, baseCase, _v0) {
var tree = _v0.c;
var tail = _v0.d;
var helper = F2(
function (node, acc) {
if (node.$ === 'SubTree') {
var subTree = node.a;
return A3($elm$core$Elm$JsArray$foldr, helper, acc, subTree);
} else {
var values = node.a;
return A3($elm$core$Elm$JsArray$foldr, func, acc, values);
}
});
return A3(
$elm$core$Elm$JsArray$foldr,
helper,
A3($elm$core$Elm$JsArray$foldr, func, baseCase, tail),
tree);
});
var $elm$core$Array$toList = function (array) {
return A3($elm$core$Array$foldr, $elm$core$List$cons, _List_Nil, array);
};
var $elm$core$Dict$foldr = F3( var $elm$core$Dict$foldr = F3(
function (func, acc, t) { function (func, acc, t) {
foldr: foldr:
@ -4707,7 +4684,30 @@ var $elm$core$Set$toList = function (_v0) {
var dict = _v0.a; var dict = _v0.a;
return $elm$core$Dict$keys(dict); return $elm$core$Dict$keys(dict);
}; };
var $elm$core$Basics$GT = {$: 'GT'}; var $elm$core$Elm$JsArray$foldr = _JsArray_foldr;
var $elm$core$Array$foldr = F3(
function (func, baseCase, _v0) {
var tree = _v0.c;
var tail = _v0.d;
var helper = F2(
function (node, acc) {
if (node.$ === 'SubTree') {
var subTree = node.a;
return A3($elm$core$Elm$JsArray$foldr, helper, acc, subTree);
} else {
var values = node.a;
return A3($elm$core$Elm$JsArray$foldr, func, acc, values);
}
});
return A3(
$elm$core$Elm$JsArray$foldr,
helper,
A3($elm$core$Elm$JsArray$foldr, func, baseCase, tail),
tree);
});
var $elm$core$Array$toList = function (array) {
return A3($elm$core$Array$foldr, $elm$core$List$cons, _List_Nil, array);
};
var $elm$core$Result$Err = function (a) { var $elm$core$Result$Err = function (a) {
return {$: 'Err', a: a}; return {$: 'Err', a: a};
}; };
@ -5417,6 +5417,8 @@ var $elm$core$Task$perform = F2(
A2($elm$core$Task$map, toMessage, task))); A2($elm$core$Task$map, toMessage, task)));
}); });
var $elm$browser$Browser$element = _Browser_element; var $elm$browser$Browser$element = _Browser_element;
var $elm$json$Json$Decode$float = _Json_decodeFloat;
var $elm$core$String$fromFloat = _String_fromNumber;
var $author$project$PhotoGroove$GotPhotos = function (a) { var $author$project$PhotoGroove$GotPhotos = function (a) {
return {$: 'GotPhotos', a: a}; return {$: 'GotPhotos', a: a};
}; };
@ -6302,11 +6304,29 @@ var $author$project$PhotoGroove$initialCommand = $elm$http$Http$get(
$elm$json$Json$Decode$list($author$project$PhotoGroove$photoDecoder)), $elm$json$Json$Decode$list($author$project$PhotoGroove$photoDecoder)),
url: 'list' url: 'list'
}); });
var $author$project$PhotoGroove$Large = {$: 'Large'};
var $author$project$PhotoGroove$Loading = {$: 'Loading'}; var $author$project$PhotoGroove$Loading = {$: 'Loading'};
var $author$project$PhotoGroove$initialModel = {chosenSize: $author$project$PhotoGroove$Large, status: $author$project$PhotoGroove$Loading}; var $author$project$PhotoGroove$Medium = {$: 'Medium'};
var $elm$core$Platform$Sub$batch = _Platform_batch; var $author$project$PhotoGroove$initialModel = {
var $elm$core$Platform$Sub$none = $elm$core$Platform$Sub$batch(_List_Nil); activity: '',
chosenSize: $author$project$PhotoGroove$Medium,
filterValues: {hue: 0, noise: 0, ripple: 0},
status: $author$project$PhotoGroove$Loading
};
var $author$project$PhotoGroove$init = function (flags) {
var activity = 'Initializing Pasta v' + $elm$core$String$fromFloat(flags);
return _Utils_Tuple2(
_Utils_update(
$author$project$PhotoGroove$initialModel,
{activity: activity}),
$author$project$PhotoGroove$initialCommand);
};
var $author$project$PhotoGroove$GotActivity = function (a) {
return {$: 'GotActivity', a: a};
};
var $author$project$PhotoGroove$activityChanges = _Platform_incomingPort('activityChanges', $elm$json$Json$Decode$string);
var $author$project$PhotoGroove$subscriptions = function (_v0) {
return $author$project$PhotoGroove$activityChanges($author$project$PhotoGroove$GotActivity);
};
var $author$project$PhotoGroove$Errored = function (a) { var $author$project$PhotoGroove$Errored = function (a) {
return {$: 'Errored', a: a}; return {$: 'Errored', a: a};
}; };
@ -6317,6 +6337,107 @@ var $author$project$PhotoGroove$Loaded = F2(
function (a, b) { function (a, b) {
return {$: 'Loaded', a: a, b: b}; return {$: 'Loaded', a: a, b: b};
}); });
var $author$project$PhotoGroove$Hue = {$: 'Hue'};
var $author$project$PhotoGroove$Noise = {$: 'Noise'};
var $author$project$PhotoGroove$Ripple = {$: 'Ripple'};
var $author$project$PhotoGroove$filterTypeToName = function (t) {
switch (t.$) {
case 'Hue':
return 'Hue';
case 'Ripple':
return 'Ripple';
default:
return 'Noise';
}
};
var $elm$core$Platform$Cmd$batch = _Platform_batch;
var $elm$core$Platform$Cmd$none = $elm$core$Platform$Cmd$batch(_List_Nil);
var $elm$json$Json$Encode$float = _Json_wrap;
var $elm$json$Json$Encode$list = F2(
function (func, entries) {
return _Json_wrap(
A3(
$elm$core$List$foldl,
_Json_addEntry(func),
_Json_emptyArray(_Utils_Tuple0),
entries));
});
var $elm$json$Json$Encode$object = function (pairs) {
return _Json_wrap(
A3(
$elm$core$List$foldl,
F2(
function (_v0, obj) {
var k = _v0.a;
var v = _v0.b;
return A3(_Json_addField, k, v, obj);
}),
_Json_emptyObject(_Utils_Tuple0),
pairs));
};
var $elm$json$Json$Encode$string = _Json_wrap;
var $author$project$PhotoGroove$setFilters = _Platform_outgoingPort(
'setFilters',
function ($) {
return $elm$json$Json$Encode$object(
_List_fromArray(
[
_Utils_Tuple2(
'filters',
$elm$json$Json$Encode$list(
function ($) {
return $elm$json$Json$Encode$object(
_List_fromArray(
[
_Utils_Tuple2(
'amount',
$elm$json$Json$Encode$float($.amount)),
_Utils_Tuple2(
'name',
$elm$json$Json$Encode$string($.name))
]));
})($.filters)),
_Utils_Tuple2(
'url',
$elm$json$Json$Encode$string($.url))
]));
});
var $author$project$PhotoGroove$urlPrefix = 'http://elm-in-action.com/';
var $author$project$PhotoGroove$applyFilters = function (model) {
var _v0 = model.status;
switch (_v0.$) {
case 'Loaded':
var photos = _v0.a;
var selectedUrl = _v0.b;
var url = $author$project$PhotoGroove$urlPrefix + ('large/' + selectedUrl);
var inPecent = function (v) {
return v / 11;
};
var filters = _List_fromArray(
[
{
amount: inPecent(model.filterValues.hue),
name: $author$project$PhotoGroove$filterTypeToName($author$project$PhotoGroove$Hue)
},
{
amount: inPecent(model.filterValues.ripple),
name: $author$project$PhotoGroove$filterTypeToName($author$project$PhotoGroove$Ripple)
},
{
amount: inPecent(model.filterValues.noise),
name: $author$project$PhotoGroove$filterTypeToName($author$project$PhotoGroove$Noise)
}
]);
return _Utils_Tuple2(
model,
$author$project$PhotoGroove$setFilters(
{filters: filters, url: url}));
case 'Loading':
return _Utils_Tuple2(model, $elm$core$Platform$Cmd$none);
default:
return _Utils_Tuple2(model, $elm$core$Platform$Cmd$none);
}
};
var $elm$random$Random$Generate = function (a) { var $elm$random$Random$Generate = function (a) {
return {$: 'Generate', a: a}; return {$: 'Generate', a: a};
}; };
@ -6441,8 +6562,6 @@ var $author$project$PhotoGroove$httpErrorToString = function (err) {
return 'bad body: ' + msg; return 'bad body: ' + msg;
} }
}; };
var $elm$core$Platform$Cmd$batch = _Platform_batch;
var $elm$core$Platform$Cmd$none = $elm$core$Platform$Cmd$batch(_List_Nil);
var $elm$core$Tuple$pair = F2( var $elm$core$Tuple$pair = F2(
function (a, b) { function (a, b) {
return _Utils_Tuple2(a, b); return _Utils_Tuple2(a, b);
@ -6459,6 +6578,23 @@ var $author$project$PhotoGroove$selectUrl = F2(
return status; return status;
} }
}); });
var $author$project$PhotoGroove$setFilterValue = F3(
function (filterType, val, values) {
switch (filterType.$) {
case 'Hue':
return _Utils_update(
values,
{hue: val});
case 'Ripple':
return _Utils_update(
values,
{ripple: val});
default:
return _Utils_update(
values,
{noise: val});
}
});
var $elm$random$Random$addOne = function (value) { var $elm$random$Random$addOne = function (value) {
return _Utils_Tuple2(1, value); return _Utils_Tuple2(1, value);
}; };
@ -6547,13 +6683,12 @@ var $author$project$PhotoGroove$update = F2(
switch (msg.$) { switch (msg.$) {
case 'ClickedThumbnail': case 'ClickedThumbnail':
var thumb = msg.a; var thumb = msg.a;
return _Utils_Tuple2( return $author$project$PhotoGroove$applyFilters(
_Utils_update( _Utils_update(
model, model,
{ {
status: A2($author$project$PhotoGroove$selectUrl, thumb, model.status) status: A2($author$project$PhotoGroove$selectUrl, thumb, model.status)
}), }));
$elm$core$Platform$Cmd$none);
case 'ClickedSurpriseMe': case 'ClickedSurpriseMe':
var _v1 = model.status; var _v1 = model.status;
switch (_v1.$) { switch (_v1.$) {
@ -6584,27 +6719,34 @@ var $author$project$PhotoGroove$update = F2(
model, model,
{chosenSize: size}), {chosenSize: size}),
$elm$core$Platform$Cmd$none); $elm$core$Platform$Cmd$none);
case 'ChangedFilter':
var f = msg.a;
var val = msg.b;
return $author$project$PhotoGroove$applyFilters(
_Utils_update(
model,
{
filterValues: A3($author$project$PhotoGroove$setFilterValue, f, val, model.filterValues)
}));
case 'GotRandomPhoto': case 'GotRandomPhoto':
var photo = msg.a; var photo = msg.a;
return _Utils_Tuple2( return $author$project$PhotoGroove$applyFilters(
_Utils_update( _Utils_update(
model, model,
{ {
status: A2($author$project$PhotoGroove$selectUrl, photo.url, model.status) status: A2($author$project$PhotoGroove$selectUrl, photo.url, model.status)
}), }));
$elm$core$Platform$Cmd$none); case 'GotPhotos':
default:
if (msg.a.$ === 'Ok') { if (msg.a.$ === 'Ok') {
if (msg.a.a.b) { if (msg.a.a.b) {
var photos = msg.a.a; var photos = msg.a.a;
var firstPhoto = photos.a; var firstPhoto = photos.a;
return _Utils_Tuple2( return $author$project$PhotoGroove$applyFilters(
_Utils_update( _Utils_update(
model, model,
{ {
status: A2($author$project$PhotoGroove$Loaded, photos, firstPhoto.url) status: A2($author$project$PhotoGroove$Loaded, photos, firstPhoto.url)
}), }));
$elm$core$Platform$Cmd$none);
} else { } else {
return _Utils_Tuple2( return _Utils_Tuple2(
_Utils_update( _Utils_update(
@ -6625,9 +6767,15 @@ var $author$project$PhotoGroove$update = F2(
}), }),
$elm$core$Platform$Cmd$none); $elm$core$Platform$Cmd$none);
} }
default:
var activity = msg.a;
return _Utils_Tuple2(
_Utils_update(
model,
{activity: activity}),
$elm$core$Platform$Cmd$none);
} }
}); });
var $elm$json$Json$Encode$string = _Json_wrap;
var $elm$html$Html$Attributes$stringProperty = F2( var $elm$html$Html$Attributes$stringProperty = F2(
function (key, string) { function (key, string) {
return A2( return A2(
@ -6640,9 +6788,10 @@ var $elm$html$Html$div = _VirtualDom_node('div');
var $elm$virtual_dom$VirtualDom$text = _VirtualDom_text; var $elm$virtual_dom$VirtualDom$text = _VirtualDom_text;
var $elm$html$Html$text = $elm$virtual_dom$VirtualDom$text; var $elm$html$Html$text = $elm$virtual_dom$VirtualDom$text;
var $author$project$PhotoGroove$ClickedSurpriseMe = {$: 'ClickedSurpriseMe'}; var $author$project$PhotoGroove$ClickedSurpriseMe = {$: 'ClickedSurpriseMe'};
var $author$project$PhotoGroove$Medium = {$: 'Medium'}; var $author$project$PhotoGroove$Large = {$: 'Large'};
var $author$project$PhotoGroove$Small = {$: 'Small'}; var $author$project$PhotoGroove$Small = {$: 'Small'};
var $elm$html$Html$button = _VirtualDom_node('button'); var $elm$html$Html$button = _VirtualDom_node('button');
var $elm$html$Html$canvas = _VirtualDom_node('canvas');
var $elm$html$Html$h1 = _VirtualDom_node('h1'); var $elm$html$Html$h1 = _VirtualDom_node('h1');
var $elm$html$Html$h3 = _VirtualDom_node('h3'); var $elm$html$Html$h3 = _VirtualDom_node('h3');
var $elm$html$Html$Attributes$id = $elm$html$Html$Attributes$stringProperty('id'); var $elm$html$Html$Attributes$id = $elm$html$Html$Attributes$stringProperty('id');
@ -6680,12 +6829,85 @@ var $elm$html$Html$Attributes$src = function (url) {
'src', 'src',
_VirtualDom_noJavaScriptOrHtmlUri(url)); _VirtualDom_noJavaScriptOrHtmlUri(url));
}; };
var $author$project$PhotoGroove$urlPrefix = 'http://elm-in-action.com/'; var $author$project$PhotoGroove$ChangedFilter = F2(
function (a, b) {
return {$: 'ChangedFilter', a: a, b: b};
});
var $elm$json$Json$Encode$int = _Json_wrap;
var $elm$html$Html$label = _VirtualDom_node('label');
var $elm$html$Html$Attributes$max = $elm$html$Html$Attributes$stringProperty('max');
var $author$project$PhotoGroove$onSlide = function (toMsg) {
return A2(
$elm$html$Html$Events$on,
'slide',
A2(
$elm$json$Json$Decode$map,
toMsg,
A2(
$elm$json$Json$Decode$at,
_List_fromArray(
['detail', 'userSlidTo']),
$elm$json$Json$Decode$int)));
};
var $elm$virtual_dom$VirtualDom$property = F2(
function (key, value) {
return A2(
_VirtualDom_property,
_VirtualDom_noInnerHtmlOrFormAction(key),
_VirtualDom_noJavaScriptOrHtmlJson(value));
});
var $elm$html$Html$Attributes$property = $elm$virtual_dom$VirtualDom$property;
var $elm$virtual_dom$VirtualDom$node = function (tag) {
return _VirtualDom_node(
_VirtualDom_noScript(tag));
};
var $elm$html$Html$node = $elm$virtual_dom$VirtualDom$node;
var $author$project$PhotoGroove$rangeSlider = $elm$html$Html$node('range-slider');
var $author$project$PhotoGroove$viewFilter = F2(
function (filterType, magnitude) {
return A2(
$elm$html$Html$div,
_List_fromArray(
[
$elm$html$Html$Attributes$class('filter-slider')
]),
_List_fromArray(
[
A2(
$elm$html$Html$label,
_List_Nil,
_List_fromArray(
[
$elm$html$Html$text(
$author$project$PhotoGroove$filterTypeToName(filterType))
])),
A2(
$author$project$PhotoGroove$rangeSlider,
_List_fromArray(
[
$elm$html$Html$Attributes$max('11'),
A2(
$elm$html$Html$Attributes$property,
'val',
$elm$json$Json$Encode$int(magnitude)),
$author$project$PhotoGroove$onSlide(
$author$project$PhotoGroove$ChangedFilter(filterType))
]),
_List_Nil),
A2(
$elm$html$Html$label,
_List_Nil,
_List_fromArray(
[
$elm$html$Html$text(
$elm$core$String$fromInt(magnitude))
]))
]));
});
var $author$project$PhotoGroove$ClickedSize = function (a) { var $author$project$PhotoGroove$ClickedSize = function (a) {
return {$: 'ClickedSize', a: a}; return {$: 'ClickedSize', a: a};
}; };
var $elm$html$Html$input = _VirtualDom_node('input'); var $elm$html$Html$input = _VirtualDom_node('input');
var $elm$html$Html$label = _VirtualDom_node('label');
var $elm$html$Html$Attributes$name = $elm$html$Html$Attributes$stringProperty('name'); var $elm$html$Html$Attributes$name = $elm$html$Html$Attributes$stringProperty('name');
var $author$project$PhotoGroove$sizeToString = function (size) { var $author$project$PhotoGroove$sizeToString = function (size) {
switch (size.$) { switch (size.$) {
@ -6770,7 +6992,7 @@ var $author$project$PhotoGroove$viewThumbnail = F2(
_List_Nil); _List_Nil);
}); });
var $author$project$PhotoGroove$viewLoaded = F3( var $author$project$PhotoGroove$viewLoaded = F3(
function (photos, selected, size) { function (model, photos, selected) {
return _List_fromArray( return _List_fromArray(
[ [
A2( A2(
@ -6791,6 +7013,28 @@ var $author$project$PhotoGroove$viewLoaded = F3(
$elm$html$Html$text('Surprise me!') $elm$html$Html$text('Surprise me!')
])), ])),
A2( A2(
$elm$html$Html$div,
_List_fromArray(
[
$elm$html$Html$Attributes$class('activity')
]),
_List_fromArray(
[
$elm$html$Html$text(model.activity)
])),
A2(
$elm$html$Html$div,
_List_fromArray(
[
$elm$html$Html$Attributes$class('filters')
]),
_List_fromArray(
[
A2($author$project$PhotoGroove$viewFilter, $author$project$PhotoGroove$Hue, model.filterValues.hue),
A2($author$project$PhotoGroove$viewFilter, $author$project$PhotoGroove$Ripple, model.filterValues.ripple),
A2($author$project$PhotoGroove$viewFilter, $author$project$PhotoGroove$Noise, model.filterValues.noise)
])),
A2(
$elm$html$Html$h3, $elm$html$Html$h3,
_List_Nil, _List_Nil,
_List_fromArray( _List_fromArray(
@ -6814,7 +7058,7 @@ var $author$project$PhotoGroove$viewLoaded = F3(
[ [
$elm$html$Html$Attributes$id('thumbnails'), $elm$html$Html$Attributes$id('thumbnails'),
$elm$html$Html$Attributes$class( $elm$html$Html$Attributes$class(
$author$project$PhotoGroove$sizeToClass(size)) $author$project$PhotoGroove$sizeToClass(model.chosenSize))
]), ]),
A2( A2(
$elm$core$List$map, $elm$core$List$map,
@ -6827,6 +7071,14 @@ var $author$project$PhotoGroove$viewLoaded = F3(
$elm$html$Html$Attributes$class('large'), $elm$html$Html$Attributes$class('large'),
$elm$html$Html$Attributes$src($author$project$PhotoGroove$urlPrefix + ('large/' + selected)) $elm$html$Html$Attributes$src($author$project$PhotoGroove$urlPrefix + ('large/' + selected))
]), ]),
_List_Nil),
A2(
$elm$html$Html$canvas,
_List_fromArray(
[
$elm$html$Html$Attributes$id('canvas-main'),
$elm$html$Html$Attributes$class('large')
]),
_List_Nil) _List_Nil)
]); ]);
}); });
@ -6845,7 +7097,7 @@ var $author$project$PhotoGroove$view = function (model) {
case 'Loaded': case 'Loaded':
var photos = _v0.a; var photos = _v0.a;
var selected = _v0.b; var selected = _v0.b;
return A3($author$project$PhotoGroove$viewLoaded, photos, selected, model.chosenSize); return A3($author$project$PhotoGroove$viewLoaded, model, photos, selected);
default: default:
var error = _v0.a; var error = _v0.a;
return _List_fromArray( return _List_fromArray(
@ -6856,15 +7108,5 @@ var $author$project$PhotoGroove$view = function (model) {
}()); }());
}; };
var $author$project$PhotoGroove$main = $elm$browser$Browser$element( var $author$project$PhotoGroove$main = $elm$browser$Browser$element(
{ {init: $author$project$PhotoGroove$init, subscriptions: $author$project$PhotoGroove$subscriptions, update: $author$project$PhotoGroove$update, view: $author$project$PhotoGroove$view});
init: function (_v0) { _Platform_export({'PhotoGroove':{'init':$author$project$PhotoGroove$main($elm$json$Json$Decode$float)(0)}});}(this));
return _Utils_Tuple2($author$project$PhotoGroove$initialModel, $author$project$PhotoGroove$initialCommand);
},
subscriptions: function (_v1) {
return $elm$core$Platform$Sub$none;
},
update: $author$project$PhotoGroove$update,
view: $author$project$PhotoGroove$view
});
_Platform_export({'PhotoGroove':{'init':$author$project$PhotoGroove$main(
$elm$json$Json$Decode$succeed(_Utils_Tuple0))(0)}});}(this));

View file

@ -2,12 +2,49 @@
<html> <html>
<head> <head>
<link rel="stylesheet" href="styles.css" /> <link rel="stylesheet" href="styles.css" />
<link rel="stylesheet" href="range-slider.css" />
<script src="./range-slider.js"></script>
<script>
class RangeSlider extends HTMLElement {
connectedCallback() {
var input = document.createElement("input");
this.appendChild(input);
var jsr = new JSR(input,
{ max: this.max,
values: [this.val],
sliders: 1,
grid: false
});
var rangeSliderNode = this;
jsr.addEventListener ("update",
function (elem, value) {
var event =
new CustomEvent ("slide",
{detail: {userSlidTo: value}});
rangeSliderNode.dispatchEvent(event);
});
}
}
window.customElements.define("range-slider", RangeSlider);
</script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script src="pasta.js"></script>
<script src="app.js"></script> <script src="app.js"></script>
<script> <script>
Elm.PhotoGroove.init({node: document.getElementById("app")}); var app = Elm.PhotoGroove.init({node: document.getElementById("app"), flags: Pasta.version});
app.ports.setFilters.subscribe (
function(options) {
requestAnimationFrame(function() {
Pasta.apply(document.getElementById("canvas-main"), options);
});}
);
Pasta.addActivityListener(function(activity) {
app.ports.activityChanges.send(activity);
});
</script> </script>
</body> </body>
</html> </html>

2418
pasta.js Normal file

File diff suppressed because it is too large Load diff

141
range-slider.css Normal file
View file

@ -0,0 +1,141 @@
/* https://github.com/mm-jsr/jsr
Copyright 2017 Mateusz "Soanvig" Koteja
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
.jsr, .jsr_bar, .jsr_label, .jsr_rail,
.jsr_rail-outer, .jsr_slider {
box-sizing: border-box;
}
.jsr {
position: relative;
z-index: 1;
display: block;
box-sizing: border-box;
width: 100%;
margin: 20px 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-touch-callout: none;
-khtml-user-select: none;
font: 14px sans-serif;
}
.jsr_rail-outer {
position: relative;
padding: 10px 0;
cursor: pointer;
}
.jsr_rail {
height: 5px;
background: #444;
z-index: 1;
}
.jsr_bar {
position: absolute;
height: 5px;
background-color: #999;
z-index: 2;
cursor: move;
}
.jsr_bar--limit {
background-color: #7e7e7e;
z-index: 1;
cursor: auto;
}
.jsr_slider {
position: absolute;
top: calc(5px / 2 + 10px);
left: 0;
transform: translate(-50%, -50%);
width: 25px;
height: 25px;
cursor: col-resize;
transition: background 0.1s ease-in-out;
outline: 0;
z-index: 3;
}
.jsr_slider::before {
content: '';
width: 15px;
height: 15px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #999;
border-radius: 50%;
}
.jsr_slider:focus::before {
background: #c00;
}
.jsr_label {
position: absolute;
top: calc(10px + 5px + 15px / 1.5);
padding: 0.2em 0.4em;
background: #444;
color: #fff;
font-size: 0.9em;
white-space: nowrap;
border-radius: 0.3em;
z-index: 2;
}
.jsr_label--minmax {
z-index: 1;
color: #999;
background: #333;
transition: opacity 0.2s ease-in-out;
}
/* Merged labels */
.jsr_label .jsr_label {
position: static;
display: inline-block;
font-size: 1em;
padding-top: 0;
padding-right: 0;
padding-bottom: 0;
}
.jsr_label .jsr_label::before {
content: ' - ';
}
.jsr_canvas {
margin-top: 5px;
}
/* Lock screen for touch */
.jsr_lockscreen {
overflow: hidden;
height: 100%;
width: 100%;
}

12
range-slider.js Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,12 +1,13 @@
module PhotoGroove exposing (main) port module PhotoGroove exposing (main)
import Browser import Browser
import Html exposing (Html, button, div, h1, h3, img, input, label, text) import Html exposing (Attribute, Html, button, canvas, div, h1, h3, img, input, label, node, text)
import Html.Attributes exposing (..) import Html.Attributes as Attr exposing (..)
import Html.Events exposing (onClick) import Html.Events exposing (on, onClick)
import Http import Http
import Json.Decode as D exposing (Decoder) import Json.Decode as D exposing (Decoder)
import Json.Decode.Pipeline as D import Json.Decode.Pipeline as D
import Json.Encode
import Random import Random
@ -16,9 +17,30 @@ type Status
| Errored String | Errored String
type alias FilterValues =
{ hue : Int
, ripple : Int
, noise : Int
}
type alias FilterOptions =
{ url : String
, filters : List { name : String, amount : Float }
}
port setFilters : FilterOptions -> Cmd msg
port activityChanges : (String -> msg) -> Sub msg
type alias Model = type alias Model =
{ status : Status { status : Status
, chosenSize : ThumbnailSize , chosenSize : ThumbnailSize
, filterValues : FilterValues
, activity : String
} }
@ -46,7 +68,9 @@ type ThumbnailSize
initialModel : Model initialModel : Model
initialModel = initialModel =
{ status = Loading { status = Loading
, chosenSize = Large , chosenSize = Medium
, filterValues = { hue = 0, ripple = 0, noise = 0 }
, activity = ""
} }
@ -66,25 +90,32 @@ view model =
[] []
Loaded photos selected -> Loaded photos selected ->
viewLoaded photos selected model.chosenSize viewLoaded model photos selected
Errored error -> Errored error ->
[ text ("Error: " ++ error) ] [ text ("Error: " ++ error) ]
viewLoaded : List Photo -> String -> ThumbnailSize -> List (Html Message) viewLoaded : Model -> List Photo -> String -> List (Html Message)
viewLoaded photos selected size = viewLoaded model photos selected =
[ h1 [] [ text "Photo Groove" ] [ h1 [] [ text "Photo Groove" ]
, button [ onClick ClickedSurpriseMe ] [ text "Surprise me!" ] , button [ onClick ClickedSurpriseMe ] [ text "Surprise me!" ]
, div [ class "activity" ] [ text model.activity ]
, div [ class "filters" ]
[ viewFilter Hue model.filterValues.hue
, viewFilter Ripple model.filterValues.ripple
, viewFilter Noise model.filterValues.noise
]
, h3 [] [ text "Thumbnail Size:" ] , h3 [] [ text "Thumbnail Size:" ]
, div [ id "choose-size" ] , div [ id "choose-size" ]
(List.map viewSizeChooser [ Small, Medium, Large ]) (List.map viewSizeChooser [ Small, Medium, Large ])
, div , div
[ id "thumbnails" [ id "thumbnails"
, class (sizeToClass size) , class (sizeToClass model.chosenSize)
] ]
(List.map (viewThumbnail selected) photos) (List.map (viewThumbnail selected) photos)
, img [ class "large", src (urlPrefix ++ "large/" ++ selected) ] [] , img [ class "large", src (urlPrefix ++ "large/" ++ selected) ] []
, canvas [ id "canvas-main", class "large" ] []
] ]
@ -112,6 +143,22 @@ viewSizeChooser size =
] ]
viewFilter : FilterType -> Int -> Html Message
viewFilter filterType magnitude =
div
[ class "filter-slider"
]
[ label [] [ text <| filterTypeToName filterType ]
, rangeSlider
[ Attr.max "11"
, property "val" (Json.Encode.int magnitude)
, onSlide (ChangedFilter filterType)
]
[]
, label [] [ text (String.fromInt magnitude) ]
]
sizeToString : ThumbnailSize -> String sizeToString : ThumbnailSize -> String
sizeToString size = sizeToString size =
case size of case size of
@ -142,15 +189,36 @@ type Message
= ClickedThumbnail String = ClickedThumbnail String
| ClickedSurpriseMe | ClickedSurpriseMe
| ClickedSize ThumbnailSize | ClickedSize ThumbnailSize
| ChangedFilter FilterType Int
| GotRandomPhoto Photo | GotRandomPhoto Photo
| GotPhotos (Result Http.Error (List Photo)) | GotPhotos (Result Http.Error (List Photo))
| GotActivity String
type FilterType
= Hue
| Ripple
| Noise
filterTypeToName : FilterType -> String
filterTypeToName t =
case t of
Hue ->
"Hue"
Ripple ->
"Ripple"
Noise ->
"Noise"
update : Message -> Model -> ( Model, Cmd Message ) update : Message -> Model -> ( Model, Cmd Message )
update msg model = update msg model =
case msg of case msg of
ClickedThumbnail thumb -> ClickedThumbnail thumb ->
( { model | status = selectUrl thumb model.status }, Cmd.none ) applyFilters { model | status = selectUrl thumb model.status }
ClickedSurpriseMe -> ClickedSurpriseMe ->
case model.status of case model.status of
@ -171,11 +239,14 @@ update msg model =
ClickedSize size -> ClickedSize size ->
( { model | chosenSize = size }, Cmd.none ) ( { model | chosenSize = size }, Cmd.none )
ChangedFilter f val ->
applyFilters { model | filterValues = setFilterValue f val model.filterValues }
GotRandomPhoto photo -> GotRandomPhoto photo ->
( { model | status = selectUrl photo.url model.status }, Cmd.none ) applyFilters { model | status = selectUrl photo.url model.status }
GotPhotos (Ok ((firstPhoto :: _) as photos)) -> GotPhotos (Ok ((firstPhoto :: _) as photos)) ->
( { model | status = Loaded photos firstPhoto.url }, Cmd.none ) applyFilters { model | status = Loaded photos firstPhoto.url }
GotPhotos (Ok []) -> GotPhotos (Ok []) ->
( { model | status = Errored "No photos!" }, Cmd.none ) ( { model | status = Errored "No photos!" }, Cmd.none )
@ -183,6 +254,62 @@ update msg model =
GotPhotos (Err httpError) -> GotPhotos (Err httpError) ->
( { model | status = Errored <| ("Failed to load photos: " ++ httpErrorToString httpError) }, Cmd.none ) ( { model | status = Errored <| ("Failed to load photos: " ++ httpErrorToString httpError) }, Cmd.none )
GotActivity activity ->
( { model | activity = activity }, Cmd.none )
subscriptions : Model -> Sub Message
subscriptions _ =
activityChanges GotActivity
init : Float -> ( Model, Cmd Message )
init flags =
let
activity =
"Initializing Pasta v" ++ String.fromFloat flags
in
( { initialModel | activity = activity }, initialCommand )
setFilterValue : FilterType -> Int -> FilterValues -> FilterValues
setFilterValue filterType val values =
case filterType of
Hue ->
{ values | hue = val }
Ripple ->
{ values | ripple = val }
Noise ->
{ values | noise = val }
applyFilters : Model -> ( Model, Cmd msg )
applyFilters model =
case model.status of
Loaded photos selectedUrl ->
let
inPecent v =
toFloat v / 11
filters =
[ { name = filterTypeToName Hue, amount = inPecent model.filterValues.hue }
, { name = filterTypeToName Ripple, amount = inPecent model.filterValues.ripple }
, { name = filterTypeToName Noise, amount = inPecent model.filterValues.noise }
]
url =
urlPrefix ++ "large/" ++ selectedUrl
in
( model, setFilters { url = url, filters = filters } )
Loading ->
( model, Cmd.none )
Errored _ ->
( model, Cmd.none )
selectUrl : String -> Status -> Status selectUrl : String -> Status -> Status
selectUrl url status = selectUrl url status =
@ -221,11 +348,23 @@ urlPrefix =
"http://elm-in-action.com/" "http://elm-in-action.com/"
main : Program () Model Message main : Program Float Model Message
main = main =
Browser.element Browser.element
{ init = \_ -> ( initialModel, initialCommand ) { init = init
, subscriptions = \_ -> Sub.none , subscriptions = subscriptions
, view = view , view = view
, update = update , update = update
} }
rangeSlider : List (Html.Attribute msg) -> List (Html msg) -> Html msg
rangeSlider =
node "range-slider"
onSlide : (Int -> msg) -> Attribute msg
onSlide toMsg =
D.at [ "detail", "userSlidTo" ] D.int
|> D.map toMsg
|> on "slide"