Extract background color to continue image

This commit is contained in:
nemunaire 2024-03-18 11:26:01 +01:00
parent 35d07c1aa4
commit 26c282138e
23 changed files with 218 additions and 115 deletions

View File

@ -677,7 +677,7 @@ func createExercice(c *gin.Context) {
} }
} }
exercice, err := theme.AddExercice(ue.Title, ue.Authors, ue.Image, ue.WIP, ue.URLId, ue.Path, ue.Statement, ue.Overview, ue.Headline, depend, ue.Gain, ue.VideoURI, ue.Resolution, ue.SeeAlso, ue.Finished) exercice, err := theme.AddExercice(ue.Title, ue.Authors, ue.Image, ue.BackgroundColor, ue.WIP, ue.URLId, ue.Path, ue.Statement, ue.Overview, ue.Headline, depend, ue.Gain, ue.VideoURI, ue.Resolution, ue.SeeAlso, ue.Finished)
if err != nil { if err != nil {
log.Println("Unable to createExercice:", err.Error()) log.Println("Unable to createExercice:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercice creation."}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercice creation."})

View File

@ -1715,7 +1715,7 @@ angular.module("FICApp")
}) })
.controller("ThemeController", function($scope, Theme, $routeParams, $location, $rootScope, $http) { .controller("ThemeController", function($scope, Theme, $routeParams, $location, $rootScope, $http) {
$scope.theme = Theme.get({ themeId: $routeParams.themeId }); $scope.theme = Theme.get({ themeId: $routeParams.themeId });
$scope.fields = ["name", "urlid", "locked", "authors", "headline", "intro", "image", "partner_txt", "partner_href", "partner_img"]; $scope.fields = ["name", "urlid", "locked", "authors", "headline", "intro", "image", "background_color", "partner_txt", "partner_href", "partner_img"];
$scope.saveTheme = function() { $scope.saveTheme = function() {
if (this.theme.id) { if (this.theme.id) {
@ -1893,7 +1893,7 @@ angular.module("FICApp")
} }
}); });
$scope.exercices = Exercice.query(); $scope.exercices = Exercice.query();
$scope.fields = ["title", "urlid", "authors", "disabled", "statement", "headline", "overview", "finished", "depend", "gain", "coefficient", "videoURI", "image", "resolution", "issue", "issuekind", "wip"]; $scope.fields = ["title", "urlid", "authors", "disabled", "statement", "headline", "overview", "finished", "depend", "gain", "coefficient", "videoURI", "image", "background_color", "resolution", "issue", "issuekind", "wip"];
$scope.inSync = false; $scope.inSync = false;
$scope.syncExo = function() { $scope.syncExo = function() {

View File

@ -21,7 +21,7 @@
<div class="form-group row" ng-repeat="field in fields"> <div class="form-group row" ng-repeat="field in fields">
<label for="{{ field }}" class="col-sm-1 col-form-label-sm">{{ field | capitalize }}</label> <label for="{{ field }}" class="col-sm-1 col-form-label-sm">{{ field | capitalize }}</label>
<div class="col-sm-11"> <div class="col-sm-11">
<input type="text" class="form-control form-control-sm" id="{{ field }}" ng-model="exercice[field]" ng-if="field != 'statement' && field != 'issue' && field != 'issuekind' && field != 'overview' && field != 'resolution' && field != 'finished' && field != 'depend' && field != 'gain' && field != 'coefficient' && field != 'wip' && field != 'disabled'"> <input type="text" class="form-control form-control-sm" id="{{ field }}" ng-model="exercice[field]" ng-if="field != 'statement' && field != 'issue' && field != 'issuekind' && field != 'overview' && field != 'resolution' && field != 'finished' && field != 'depend' && field != 'gain' && field != 'coefficient' && field != 'wip' && field != 'disabled' && field != 'background_color'">
<input type="checkbox" id="{{ field }}" ng-model="exercice[field]" ng-if="field == 'wip' || field == 'disabled'"> <input type="checkbox" id="{{ field }}" ng-model="exercice[field]" ng-if="field == 'wip' || field == 'disabled'">
<input type="text" class="form-control form-control-sm" id="{{ field }}" ng-model="exercice[field]" ng-if="field == 'gain'" integer> <input type="text" class="form-control form-control-sm" id="{{ field }}" ng-model="exercice[field]" ng-if="field == 'gain'" integer>
<input type="text" class="form-control form-control-sm" id="{{ field }}" ng-model="exercice[field]" ng-if="field == 'coefficient'" float> <input type="text" class="form-control form-control-sm" id="{{ field }}" ng-model="exercice[field]" ng-if="field == 'coefficient'" float>
@ -30,6 +30,7 @@
<option value="">Aucune</option> <option value="">Aucune</option>
</select> </select>
<select class="form-control form-control-sm" id="{{field}}" ng-model="exercice[field]" ng-options="v for v in ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark']" ng-if="field == 'issuekind'"></select> <select class="form-control form-control-sm" id="{{field}}" ng-model="exercice[field]" ng-options="v for v in ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark']" ng-if="field == 'issuekind'"></select>
<input type="color" class="form-control form-control-sm" id="{{ field }}" ng-model="exercice[field]" ng-if="field == 'background_color'" color>
</div> </div>
</div> </div>
<div class="text-right" ng-show="exercice.id"> <div class="text-right" ng-show="exercice.id">

View File

@ -12,8 +12,9 @@
<div ng-class="{'form-group': field != 'locked', 'form-check': field == 'locked'}" ng-repeat="field in fields"> <div ng-class="{'form-group': field != 'locked', 'form-check': field == 'locked'}" ng-repeat="field in fields">
<input type="checkbox" class="form-check-input" id="{{ field }}" ng-model="theme[field]" ng-if="field == 'locked'"> <input type="checkbox" class="form-check-input" id="{{ field }}" ng-model="theme[field]" ng-if="field == 'locked'">
<label for="{{ field }}">{{ field | capitalize }}</label> <label for="{{ field }}">{{ field | capitalize }}</label>
<input type="text" class="form-control form-control-sm" id="{{ field }}" ng-model="theme[field]" ng-if="field != 'intro' && field != 'locked'"> <input type="text" class="form-control form-control-sm" id="{{ field }}" ng-model="theme[field]" ng-if="field != 'intro' && field != 'locked' && field != 'background_color'">
<textarea class="form-control form-control-sm" id="{{ field }}" ng-model="theme[field]" ng-if="field == 'intro'"></textarea> <textarea class="form-control form-control-sm" id="{{ field }}" ng-model="theme[field]" ng-if="field == 'intro'"></textarea>
<input type="color" class="form-control form-control-sm" id="{{ field }}" ng-model="theme[field]" ng-if="field == 'background_color'" color>
</div> </div>
<div class="text-right" ng-show="theme.id"> <div class="text-right" ng-show="theme.id">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-save" aria-hidden="true"></span> Save</button> <button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-save" aria-hidden="true"></span> Save</button>

View File

@ -397,6 +397,8 @@ func SyncExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*f
e.Image = strings.TrimPrefix(filePath, fic.FilesDir) e.Image = strings.TrimPrefix(filePath, fic.FilesDir)
e.BackgroundColor, _ = getBackgroundColor(filePath)
// If the theme has no image yet, use the first exercice's image found // If the theme has no image yet, use the first exercice's image found
theme.Image = e.Image theme.Image = e.Image
_, err := theme.Update() _, err := theme.Update()

View File

@ -13,6 +13,7 @@ import (
"strings" "strings"
"unicode" "unicode"
"github.com/cenkalti/dominantcolor"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/yuin/goldmark" "github.com/yuin/goldmark"
"go.uber.org/multierr" "go.uber.org/multierr"
@ -86,6 +87,49 @@ func resizePicture(importedPath string, rect image.Rectangle) error {
return nil return nil
} }
type SubImager interface {
SubImage(r image.Rectangle) image.Image
}
// getBackgroundColor retrieves the most dominant color in the bottom of the image.
func getBackgroundColor(importedPath string) (uint32, error) {
fl, err := os.Open(strings.TrimSuffix(importedPath, ".jpg") + ".thumb.jpg")
if err != nil {
return 0, err
}
src, _, err := image.Decode(fl)
if err != nil {
fl.Close()
return 0, err
}
bounds := src.Bounds()
// Test if the right and left corner have the same color
bottomLeft := src.(SubImager).SubImage(image.Rect(0, bounds.Dy()-10, 40, bounds.Dy()))
bottomRight := src.(SubImager).SubImage(image.Rect(bounds.Dx()-40, bounds.Dy()-10, bounds.Dx(), bounds.Dy()))
colorLeft := dominantcolor.Find(bottomLeft)
colorRight := dominantcolor.Find(bottomRight)
if uint32(colorLeft.R>>5)<<16+uint32(colorLeft.G>>5)<<8+uint32(colorLeft.B>>5) == uint32(colorRight.R>>5)<<16+uint32(colorRight.G>>5)<<8+uint32(colorRight.B>>5) {
return uint32(colorLeft.R)<<16 + uint32(colorLeft.G)<<8 + uint32(colorLeft.B), nil
}
// Only keep the darkest color of the bottom of the image
bottomFull := src.(SubImager).SubImage(image.Rect(0, bounds.Dy()-5, bounds.Dx(), bounds.Dy()))
colors := dominantcolor.FindN(bottomFull, 4)
color := colors[0]
for _, c := range colors {
if uint32(color.R<<2)+uint32(color.G<<2)+uint32(color.B<<2) > uint32(c.R<<2)+uint32(c.G<<2)+uint32(c.B<<2) {
color = c
}
}
return uint32(color.R)<<16 + uint32(color.G)<<8 + uint32(color.B), nil
}
// getAuthors parses the AUTHORS file. // getAuthors parses the AUTHORS file.
func getAuthors(i Importer, tname string) ([]string, error) { func getAuthors(i Importer, tname string) ([]string, error) {
if authors, err := GetFileContent(i, path.Join(tname, "AUTHORS.txt")); err != nil { if authors, err := GetFileContent(i, path.Join(tname, "AUTHORS.txt")); err != nil {
@ -258,6 +302,7 @@ func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, errs error)
} }
btheme.Image = strings.TrimPrefix(filePath, fic.FilesDir) btheme.Image = strings.TrimPrefix(filePath, fic.FilesDir)
btheme.BackgroundColor, _ = getBackgroundColor(filePath)
return nil, nil return nil, nil
}); err != nil { }); err != nil {
errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("unable to import heading image: %w", err))) errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("unable to import heading image: %w", err)))

View File

@ -22,6 +22,9 @@
import { settings } from '$lib/stores/settings.js'; import { settings } from '$lib/stores/settings.js';
import { themesStore } from '$lib/stores/themes.js'; import { themesStore } from '$lib/stores/themes.js';
// Override theme color
document.body.style.backgroundColor = "";
let items = []; let items = [];
$: { $: {
const tmpitems = []; const tmpitems = [];

View File

@ -12,7 +12,21 @@
let heading_image = ""; let heading_image = "";
let current_authors = ""; let current_authors = "";
let background_color = "#ffffff";
let color_brightness = 0;
$: color_brightness = parseInt(background_color[1], 16) + parseInt(background_color[3], 16) + parseInt(background_color[5], 16);
$: if ($current_theme) { $: if ($current_theme) {
if ($current_exercice && $current_exercice.background_color) {
background_color = $current_exercice.background_color;
document.body.style.backgroundColor = $current_exercice.background_color;
} else if ($current_theme.background_color) {
background_color = $current_theme.background_color;
document.body.style.backgroundColor = $current_theme.background_color;
} else {
background_color = "#ffffff";
document.body.style.backgroundColor = "";
}
if ($current_exercice && $current_exercice.image) { if ($current_exercice && $current_exercice.image) {
heading_image = $current_exercice.image; heading_image = $current_exercice.image;
} else { } else {
@ -48,13 +62,21 @@
</Container> </Container>
{:else} {:else}
<div style="background-image: url({heading_image})" class="page-header"> <div style="background-image: url({heading_image})" class="page-header">
<Container class="text-primary"> <Container class="text-primary py-4">
<h1 class="display-2"> <h1
{#if $current_theme.urlid == "_" && $current_exercice} class="display-2"
<a href="{$current_theme.urlid}">{$current_exercice.title}</a> style:text-shadow={color_brightness < 24 ? "0 0 15px rgba(255,255,255,0.95), 0 0 5px rgb(255,255,255)" : "0 0 15px rgba(0,0,0,0.95), 0 0 5px rgb(0,0,0)"}
{:else} >
<a href="{$current_theme.urlid}">{$current_theme.name}</a> <a
{/if} href="{$current_theme.urlid}"
style:color={background_color}
>
{#if $current_theme.urlid == "_" && $current_exercice}
{$current_exercice.title}
{:else}
{$current_theme.name}
{/if}
</a>
</h1> </h1>
<h2> <h2>
{#if current_authors} {#if current_authors}
@ -64,29 +86,21 @@
{/if} {/if}
</h2> </h2>
</Container> </Container>
{#if heading_image} <Container class="pb-4">
<div class="headerfade"></div> <slot></slot>
{:else} </Container>
<div style="height: 3rem;"></div>
{/if}
</div> </div>
<Container>
<slot></slot>
</Container>
{/if} {/if}
<style> <style>
.page-header { .page-header {
background-size: cover; background-size: 100% auto;
background-position: center; background-position: top center;
margin-bottom: -15rem; background-repeat: no-repeat;
}
.page-header h1 {
text-shadow: 0 0 15px rgba(255,255,255,0.95), 0 0 5px rgb(255,255,255)
} }
.page-header h1, .page-header h1 a { .page-header h1, .page-header h1 a {
color: black;
text-decoration: none; text-decoration: none;
filter: invert(100%) hue-rotate(45deg) brightness(100%);
} }
.page-header h2 { .page-header h2 {
font-size: 100%; font-size: 100%;
@ -99,16 +113,9 @@
text-decoration: underline; text-decoration: underline;
} }
.page-header h1 { .page-header h1 {
padding-top: 4rem;
text-align: center; text-align: center;
} }
.page-header h2 { .page-header h2 {
padding-bottom: 14rem;
text-align: center; text-align: center;
} }
.page-header .headerfade {
background: linear-gradient(transparent 0%, rgb(233,236,239) 100%);
height: 3rem;
}
</style> </style>

View File

@ -37,7 +37,7 @@
/> />
</Masonry> </Masonry>
{:else} {:else}
<Card class="bg-dark niceborder text-indent mt-2 mb-4"> <Card class="bg-dark niceborder text-indent mt-2" style="--bs-bg-opacity: .9;">
<Row> <Row>
<Col lg={6} xl={7}> <Col lg={6} xl={7}>

View File

@ -51,8 +51,8 @@
</script> </script>
{#if $current_exercice} {#if $current_exercice}
<Card class="niceborder text-indent my-3"> <Card class="niceborder text-indent my-3 bg-primary" style="--bs-bg-opacity: .9;">
<CardBody class="bg-dark"> <CardBody class="bg-dark" style="--bs-bg-opacity: .5;">
{#if $current_theme.locked} {#if $current_theme.locked}
<div style="position: absolute; z-index: 0; top: 0; bottom: 0; left: 0; right: 0;" class="d-flex justify-content-center align-items-center"> <div style="position: absolute; z-index: 0; top: 0; bottom: 0; left: 0; right: 0;" class="d-flex justify-content-center align-items-center">
<div style="transform: rotate(-25deg)"> <div style="transform: rotate(-25deg)">
@ -91,7 +91,7 @@
<CardBody> <CardBody>
<Row> <Row>
<Col> <Col>
<Row class="level" cols={{xs:2, md:3, xl:4}}> <Row class="level text-light" cols={{xs:2, md:3, xl:4}}>
<Col> <Col>
<div class="level-item"> <div class="level-item">
{#if $settings.discountedFactor > 0 && $my && $my.exercices[$current_exercice.id]} {#if $settings.discountedFactor > 0 && $my && $my.exercices[$current_exercice.id]}

View File

@ -17,6 +17,9 @@
import { my } from '$lib/stores/my.js'; import { my } from '$lib/stores/my.js';
import { settings } from '$lib/stores/settings.js'; import { settings } from '$lib/stores/settings.js';
// Override theme color
document.body.style.backgroundColor = "";
</script> </script>
<Container class="my-3"> <Container class="my-3">

View File

@ -17,6 +17,9 @@
import FormIssue from '$lib/components/FormIssue.svelte'; import FormIssue from '$lib/components/FormIssue.svelte';
// Override theme color
document.body.style.backgroundColor = "";
export let data; export let data;
let issue = {}; let issue = {};

View File

@ -17,6 +17,9 @@
import CardTheme from '$lib/components/CardTheme.svelte'; import CardTheme from '$lib/components/CardTheme.svelte';
let search = ""; let search = "";
// Override theme color
document.body.style.backgroundColor = "";
</script> </script>
<Container fluid class="my-3"> <Container fluid class="my-3">

View File

@ -85,6 +85,9 @@
message = "Une erreur est survenue lors de l'inscription de l'équipe. Veuillez réessayer dans quelques instants."; message = "Une erreur est survenue lors de l'inscription de l'équipe. Veuillez réessayer dans quelques instants.";
} }
} }
// Override theme color
document.body.style.backgroundColor = "";
</script> </script>
<Container class="my-3"> <Container class="my-3">

View File

@ -8,6 +8,9 @@
import { challengeInfo } from '$lib/stores/challengeinfo.js'; import { challengeInfo } from '$lib/stores/challengeinfo.js';
import { settings } from '$lib/stores/settings.js'; import { settings } from '$lib/stores/settings.js';
// Override theme color
document.body.style.backgroundColor = "";
</script> </script>
<Container class="my-3"> <Container class="my-3">

View File

@ -11,6 +11,9 @@
import { themesStore } from '$lib/stores/themes.js'; import { themesStore } from '$lib/stores/themes.js';
// Override theme color
document.body.style.backgroundColor = "";
tick().then(() => { tick().then(() => {
WordCloud( WordCloud(
document.getElementById('wordcloud'), document.getElementById('wordcloud'),

View File

@ -33,6 +33,9 @@
exercices = tmp_exercices; exercices = tmp_exercices;
} }
// Override theme color
document.body.style.backgroundColor = "";
</script> </script>
<Container class="mt-3"> <Container class="mt-3">

2
go.mod
View File

@ -31,9 +31,11 @@ require (
github.com/asticode/go-astits v1.8.0 // indirect github.com/asticode/go-astits v1.8.0 // indirect
github.com/aws/aws-sdk-go v1.38.20 // indirect github.com/aws/aws-sdk-go v1.38.20 // indirect
github.com/bytedance/sonic v1.9.1 // indirect github.com/bytedance/sonic v1.9.1 // indirect
github.com/cenkalti/dominantcolor v1.0.2 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/cloudflare/circl v1.3.3 // indirect github.com/cloudflare/circl v1.3.3 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect

3
go.sum
View File

@ -65,6 +65,8 @@ github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/cenkalti/dominantcolor v1.0.2 h1:nP1qLG2sD4vu+mGjvEcp3zMaiT7OvcRDtp+wE0YEtfg=
github.com/cenkalti/dominantcolor v1.0.2/go.mod h1:HvN7ziRLPAes3UkUrLDDRADCPTFsKUzZx5ZAQx8KECc=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
@ -78,6 +80,7 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=

View File

@ -94,6 +94,7 @@ CREATE TABLE IF NOT EXISTS themes(
headline TEXT NOT NULL, headline TEXT NOT NULL,
intro TEXT NOT NULL, intro TEXT NOT NULL,
image VARCHAR(255) NOT NULL, image VARCHAR(255) NOT NULL,
background_color INTEGER UNSIGNED NOT NULL,
authors TEXT NOT NULL, authors TEXT NOT NULL,
partner_img VARCHAR(255) NOT NULL, partner_img VARCHAR(255) NOT NULL,
partner_href VARCHAR(255) NOT NULL, partner_href VARCHAR(255) NOT NULL,
@ -106,7 +107,7 @@ CREATE TABLE IF NOT EXISTS themes(
CREATE TABLE IF NOT EXISTS teams( CREATE TABLE IF NOT EXISTS teams(
id_team INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, id_team INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
color INTEGER NOT NULL, color INTEGER UNSIGNED NOT NULL,
active BOOLEAN NOT NULL DEFAULT 1, active BOOLEAN NOT NULL DEFAULT 1,
external_id TEXT NOT NULL, external_id TEXT NOT NULL,
password VARCHAR(255) NULL password VARCHAR(255) NULL
@ -144,6 +145,7 @@ CREATE TABLE IF NOT EXISTS exercices(
title VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL,
authors TEXT NOT NULL, authors TEXT NOT NULL,
image VARCHAR(255) NOT NULL, image VARCHAR(255) NOT NULL,
background_color INTEGER UNSIGNED NOT NULL,
disabled BOOLEAN NOT NULL DEFAULT 0, disabled BOOLEAN NOT NULL DEFAULT 0,
headline TEXT NOT NULL, headline TEXT NOT NULL,
url_id VARCHAR(255) NOT NULL, url_id VARCHAR(255) NOT NULL,
@ -564,7 +566,7 @@ func DBRecreateDiscountedView() (err error) {
return return
} }
_, err = db.Exec("CREATE OR REPLACE VIEW exercices_discounted AS SELECT E.id_exercice, E.id_theme, E.title, E.authors, E.image, E.disabled, E.headline, E.url_id, E.path, E.statement, E.overview, E.issue, E.issue_kind, E.depend, E.gain - " + fmt.Sprintf("%f", DiscountedFactor) + " * E.gain * (COUNT(*) - 1) AS gain, E.coefficient_cur, E.finished, E.video_uri, E.resolution, E.seealso FROM exercices E LEFT OUTER JOIN exercice_solved S ON S.id_exercice = E.id_exercice GROUP BY E.id_exercice;") _, err = db.Exec("CREATE OR REPLACE VIEW exercices_discounted AS SELECT E.id_exercice, E.id_theme, E.title, E.authors, E.image, E.background_color, E.disabled, E.headline, E.url_id, E.path, E.statement, E.overview, E.issue, E.issue_kind, E.depend, E.gain - " + fmt.Sprintf("%f", DiscountedFactor) + " * E.gain * (COUNT(*) - 1) AS gain, E.coefficient_cur, E.finished, E.video_uri, E.resolution, E.seealso FROM exercices E LEFT OUTER JOIN exercice_solved S ON S.id_exercice = E.id_exercice GROUP BY E.id_exercice;")
return return
} }

View File

@ -21,12 +21,13 @@ var ExerciceCurrentCoefficient = 1.0
// Exercice represents a challenge inside a Theme. // Exercice represents a challenge inside a Theme.
type Exercice struct { type Exercice struct {
Id int64 `json:"id"` Id int64 `json:"id"`
IdTheme *int64 `json:"id_theme"` IdTheme *int64 `json:"id_theme"`
Language string `json:"lang,omitempty"` Language string `json:"lang,omitempty"`
Title string `json:"title"` Title string `json:"title"`
Authors string `json:"authors"` Authors string `json:"authors"`
Image string `json:"image"` Image string `json:"image"`
BackgroundColor uint32 `json:"background_color,omitempty"`
// Disabled indicates if the exercice is available to players now or not // Disabled indicates if the exercice is available to players now or not
Disabled bool `json:"disabled"` Disabled bool `json:"disabled"`
// WIP indicates if the exercice is in development or not // WIP indicates if the exercice is in development or not
@ -75,7 +76,7 @@ func (e *Exercice) AnalyzeTitle() {
func getExercice(table, condition string, args ...interface{}) (*Exercice, error) { func getExercice(table, condition string, args ...interface{}) (*Exercice, error) {
var e Exercice var e Exercice
var tmpgain float64 var tmpgain float64
if err := DBQueryRow("SELECT id_exercice, id_theme, title, authors, image, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM "+table+" "+condition, args...).Scan(&e.Id, &e.IdTheme, &e.Title, &e.Authors, &e.Image, &e.Disabled, &e.URLId, &e.Path, &e.Statement, &e.Overview, &e.Headline, &e.Issue, &e.IssueKind, &e.Depend, &tmpgain, &e.Coefficient, &e.VideoURI, &e.Resolution, &e.SeeAlso, &e.Finished); err != nil { if err := DBQueryRow("SELECT id_exercice, id_theme, title, authors, image, background_color, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM "+table+" "+condition, args...).Scan(&e.Id, &e.IdTheme, &e.Title, &e.Authors, &e.Image, &e.BackgroundColor, &e.Disabled, &e.URLId, &e.Path, &e.Statement, &e.Overview, &e.Headline, &e.Issue, &e.IssueKind, &e.Depend, &tmpgain, &e.Coefficient, &e.VideoURI, &e.Resolution, &e.SeeAlso, &e.Finished); err != nil {
return nil, err return nil, err
} }
e.Gain = int64(math.Trunc(tmpgain)) e.Gain = int64(math.Trunc(tmpgain))
@ -142,7 +143,7 @@ func GetDiscountedExercice(id int64) (*Exercice, error) {
// getExercices returns the list of all challenges present in the database. // getExercices returns the list of all challenges present in the database.
func getExercices(table string) ([]*Exercice, error) { func getExercices(table string) ([]*Exercice, error) {
if rows, err := DBQuery("SELECT id_exercice, id_theme, title, authors, image, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM " + table + " ORDER BY path ASC"); err != nil { if rows, err := DBQuery("SELECT id_exercice, id_theme, title, authors, image, background_color, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM " + table + " ORDER BY path ASC"); err != nil {
return nil, err return nil, err
} else { } else {
defer rows.Close() defer rows.Close()
@ -151,7 +152,7 @@ func getExercices(table string) ([]*Exercice, error) {
for rows.Next() { for rows.Next() {
e := &Exercice{} e := &Exercice{}
var tmpgain float64 var tmpgain float64
if err := rows.Scan(&e.Id, &e.IdTheme, &e.Title, &e.Authors, &e.Image, &e.Disabled, &e.URLId, &e.Path, &e.Statement, &e.Overview, &e.Headline, &e.Issue, &e.IssueKind, &e.Depend, &tmpgain, &e.Coefficient, &e.VideoURI, &e.Resolution, &e.SeeAlso, &e.Finished); err != nil { if err := rows.Scan(&e.Id, &e.IdTheme, &e.Title, &e.Authors, &e.Image, &e.BackgroundColor, &e.Disabled, &e.URLId, &e.Path, &e.Statement, &e.Overview, &e.Headline, &e.Issue, &e.IssueKind, &e.Depend, &tmpgain, &e.Coefficient, &e.VideoURI, &e.Resolution, &e.SeeAlso, &e.Finished); err != nil {
return nil, err return nil, err
} }
e.Gain = int64(math.Trunc(tmpgain)) e.Gain = int64(math.Trunc(tmpgain))
@ -180,11 +181,11 @@ func GetDiscountedExercices() ([]*Exercice, error) {
// GetExercices returns the list of all challenges in the Theme. // GetExercices returns the list of all challenges in the Theme.
func (t *Theme) GetExercices() ([]*Exercice, error) { func (t *Theme) GetExercices() ([]*Exercice, error) {
query := "SELECT id_exercice, id_theme, title, authors, image, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM exercices WHERE id_theme IS NULL ORDER BY path ASC" query := "SELECT id_exercice, id_theme, title, authors, image, background_color, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM exercices WHERE id_theme IS NULL ORDER BY path ASC"
args := []interface{}{} args := []interface{}{}
if t.GetId() != nil { if t.GetId() != nil {
query = "SELECT id_exercice, id_theme, title, authors, image, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM exercices WHERE id_theme = ? ORDER BY path ASC" query = "SELECT id_exercice, id_theme, title, authors, image, background_color, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM exercices WHERE id_theme = ? ORDER BY path ASC"
args = append(args, t.GetId()) args = append(args, t.GetId())
} }
@ -196,7 +197,7 @@ func (t *Theme) GetExercices() ([]*Exercice, error) {
exos := []*Exercice{} exos := []*Exercice{}
for rows.Next() { for rows.Next() {
e := &Exercice{} e := &Exercice{}
if err := rows.Scan(&e.Id, &e.IdTheme, &e.Title, &e.Authors, &e.Image, &e.Disabled, &e.URLId, &e.Path, &e.Statement, &e.Overview, &e.Headline, &e.Issue, &e.IssueKind, &e.Depend, &e.Gain, &e.Coefficient, &e.VideoURI, &e.Resolution, &e.SeeAlso, &e.Finished); err != nil { if err := rows.Scan(&e.Id, &e.IdTheme, &e.Title, &e.Authors, &e.Image, &e.BackgroundColor, &e.Disabled, &e.URLId, &e.Path, &e.Statement, &e.Overview, &e.Headline, &e.Issue, &e.IssueKind, &e.Depend, &e.Gain, &e.Coefficient, &e.VideoURI, &e.Resolution, &e.SeeAlso, &e.Finished); err != nil {
return nil, err return nil, err
} }
e.AnalyzeTitle() e.AnalyzeTitle()
@ -259,7 +260,7 @@ func (t *Theme) addExercice(e *Exercice) (err error) {
if e.WIP { if e.WIP {
wip = "%" wip = "%"
} }
if res, err := DBExec("INSERT INTO exercices (id_theme, title, authors, image, disabled, url_id, path, statement, overview, finished, headline, issue, depend, gain, video_uri, resolution, seealso, issue_kind, coefficient_cur) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, "+ik+", "+cc+")", t.GetId(), wip+e.Title, e.Authors, e.Image, e.Disabled, e.URLId, e.Path, e.Statement, e.Overview, e.Finished, e.Headline, e.Issue, e.Depend, e.Gain, e.VideoURI, e.Resolution, e.SeeAlso); err != nil { if res, err := DBExec("INSERT INTO exercices (id_theme, title, authors, image, background_color, disabled, url_id, path, statement, overview, finished, headline, issue, depend, gain, video_uri, resolution, seealso, issue_kind, coefficient_cur) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, "+ik+", "+cc+")", t.GetId(), wip+e.Title, e.Authors, e.Image, e.BackgroundColor, e.Disabled, e.URLId, e.Path, e.Statement, e.Overview, e.Finished, e.Headline, e.Issue, e.Depend, e.Gain, e.VideoURI, e.Resolution, e.SeeAlso); err != nil {
return err return err
} else if eid, err := res.LastInsertId(); err != nil { } else if eid, err := res.LastInsertId(); err != nil {
return err return err
@ -271,29 +272,30 @@ func (t *Theme) addExercice(e *Exercice) (err error) {
} }
// AddExercice creates and fills a new struct Exercice and registers it into the database. // AddExercice creates and fills a new struct Exercice and registers it into the database.
func (t *Theme) AddExercice(title string, authors string, image string, wip bool, urlId string, path string, statement string, overview string, headline string, depend *Exercice, gain int64, videoURI string, resolution string, seealso string, finished string) (e *Exercice, err error) { func (t *Theme) AddExercice(title string, authors string, image string, backgroundcolor uint32, wip bool, urlId string, path string, statement string, overview string, headline string, depend *Exercice, gain int64, videoURI string, resolution string, seealso string, finished string) (e *Exercice, err error) {
var dpd *int64 = nil var dpd *int64 = nil
if depend != nil { if depend != nil {
dpd = &depend.Id dpd = &depend.Id
} }
e = &Exercice{ e = &Exercice{
Title: title, Title: title,
Authors: authors, Authors: authors,
Image: image, Image: image,
Disabled: false, BackgroundColor: backgroundcolor,
WIP: wip, Disabled: false,
URLId: urlId, WIP: wip,
Path: path, URLId: urlId,
Statement: statement, Path: path,
Overview: overview, Statement: statement,
Headline: headline, Overview: overview,
Depend: dpd, Headline: headline,
Finished: finished, Depend: dpd,
Gain: gain, Finished: finished,
VideoURI: videoURI, Gain: gain,
Resolution: resolution, VideoURI: videoURI,
SeeAlso: seealso, Resolution: resolution,
SeeAlso: seealso,
} }
err = t.addExercice(e) err = t.addExercice(e)
@ -308,7 +310,7 @@ func (e *Exercice) Update() (int64, error) {
wip = "%" wip = "%"
} }
if res, err := DBExec("UPDATE exercices SET title = ?, authors = ?, image = ?, disabled = ?, url_id = ?, path = ?, statement = ?, overview = ?, headline = ?, issue = ?, issue_kind = ?, depend = ?, gain = ?, coefficient_cur = ?, video_uri = ?, resolution = ?, seealso = ?, finished = ? WHERE id_exercice = ?", wip+e.Title, e.Authors, e.Image, e.Disabled, e.URLId, e.Path, e.Statement, e.Overview, e.Headline, e.Issue, e.IssueKind, e.Depend, e.Gain, e.Coefficient, e.VideoURI, e.Resolution, e.SeeAlso, e.Finished, e.Id); err != nil { if res, err := DBExec("UPDATE exercices SET title = ?, authors = ?, image = ?, background_color = ?, disabled = ?, url_id = ?, path = ?, statement = ?, overview = ?, headline = ?, issue = ?, issue_kind = ?, depend = ?, gain = ?, coefficient_cur = ?, video_uri = ?, resolution = ?, seealso = ?, finished = ? WHERE id_exercice = ?", wip+e.Title, e.Authors, e.Image, e.BackgroundColor, e.Disabled, e.URLId, e.Path, e.Statement, e.Overview, e.Headline, e.Issue, e.IssueKind, e.Depend, e.Gain, e.Coefficient, e.VideoURI, e.Resolution, e.SeeAlso, e.Finished, e.Id); err != nil {
return 0, err return 0, err
} else if nb, err := res.RowsAffected(); err != nil { } else if nb, err := res.RowsAffected(); err != nil {
return 0, err return 0, err

View File

@ -4,19 +4,20 @@ import ()
// Theme represents a group of challenges, to display to players // Theme represents a group of challenges, to display to players
type Theme struct { type Theme struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Language string `json:"lang,omitempty"` Language string `json:"lang,omitempty"`
Name string `json:"name"` Name string `json:"name"`
Locked bool `json:"locked"` Locked bool `json:"locked"`
URLId string `json:"urlid"` URLId string `json:"urlid"`
Path string `json:"path"` Path string `json:"path"`
Authors string `json:"authors,omitempty"` Authors string `json:"authors,omitempty"`
Intro string `json:"intro,omitempty"` Intro string `json:"intro,omitempty"`
Headline string `json:"headline,omitempty"` Headline string `json:"headline,omitempty"`
Image string `json:"image,omitempty"` Image string `json:"image,omitempty"`
PartnerImage string `json:"partner_img,omitempty"` BackgroundColor uint32 `json:"background_color,omitempty"`
PartnerLink string `json:"partner_href,omitempty"` PartnerImage string `json:"partner_img,omitempty"`
PartnerText string `json:"partner_txt,omitempty"` PartnerLink string `json:"partner_href,omitempty"`
PartnerText string `json:"partner_txt,omitempty"`
} }
func (t *Theme) GetId() *int64 { func (t *Theme) GetId() *int64 {
@ -29,12 +30,12 @@ func (t *Theme) GetId() *int64 {
// CmpTheme returns true if given Themes are identicals. // CmpTheme returns true if given Themes are identicals.
func CmpTheme(t1 *Theme, t2 *Theme) bool { func CmpTheme(t1 *Theme, t2 *Theme) bool {
return t1 != nil && t2 != nil && !(t1.Name != t2.Name || t1.URLId != t2.URLId || t1.Path != t2.Path || t1.Authors != t2.Authors || t1.Intro != t2.Intro || t1.Headline != t2.Headline || t1.Image != t2.Image || t1.PartnerImage != t2.PartnerImage || t1.PartnerLink != t2.PartnerLink || t1.PartnerText != t2.PartnerText) return t1 != nil && t2 != nil && !(t1.Name != t2.Name || t1.URLId != t2.URLId || t1.Path != t2.Path || t1.Authors != t2.Authors || t1.Intro != t2.Intro || t1.Headline != t2.Headline || t1.Image != t2.Image || t1.BackgroundColor != t2.BackgroundColor || t1.PartnerImage != t2.PartnerImage || t1.PartnerLink != t2.PartnerLink || t1.PartnerText != t2.PartnerText)
} }
// GetThemes returns a list of registered Themes from the database. // GetThemes returns a list of registered Themes from the database.
func GetThemes() ([]*Theme, error) { func GetThemes() ([]*Theme, error) {
if rows, err := DBQuery("SELECT id_theme, name, locked, url_id, path, authors, intro, headline, image, partner_img, partner_href, partner_text FROM themes"); err != nil { if rows, err := DBQuery("SELECT id_theme, name, locked, url_id, path, authors, intro, headline, image, background_color, partner_img, partner_href, partner_text FROM themes"); err != nil {
return nil, err return nil, err
} else { } else {
defer rows.Close() defer rows.Close()
@ -42,7 +43,7 @@ func GetThemes() ([]*Theme, error) {
var themes []*Theme var themes []*Theme
for rows.Next() { for rows.Next() {
t := &Theme{} t := &Theme{}
if err := rows.Scan(&t.Id, &t.Name, &t.Locked, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.PartnerImage, &t.PartnerLink, &t.PartnerText); err != nil { if err := rows.Scan(&t.Id, &t.Name, &t.Locked, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.BackgroundColor, &t.PartnerImage, &t.PartnerLink, &t.PartnerText); err != nil {
return nil, err return nil, err
} }
themes = append(themes, t) themes = append(themes, t)
@ -58,7 +59,7 @@ func GetThemes() ([]*Theme, error) {
// GetTheme retrieves a Theme from its identifier. // GetTheme retrieves a Theme from its identifier.
func GetTheme(id int64) (*Theme, error) { func GetTheme(id int64) (*Theme, error) {
t := &Theme{} t := &Theme{}
if err := DBQueryRow("SELECT id_theme, name, locked, url_id, path, authors, intro, headline, image, partner_img, partner_href, partner_text FROM themes WHERE id_theme=?", id).Scan(&t.Id, &t.Name, &t.Locked, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.PartnerImage, &t.PartnerLink, &t.PartnerText); err != nil { if err := DBQueryRow("SELECT id_theme, name, locked, url_id, path, authors, intro, headline, image, background_color, partner_img, partner_href, partner_text FROM themes WHERE id_theme=?", id).Scan(&t.Id, &t.Name, &t.Locked, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.BackgroundColor, &t.PartnerImage, &t.PartnerLink, &t.PartnerText); err != nil {
return t, err return t, err
} }
@ -68,7 +69,7 @@ func GetTheme(id int64) (*Theme, error) {
// GetTheme retrieves a Theme from an given Exercice. // GetTheme retrieves a Theme from an given Exercice.
func (e *Exercice) GetTheme() (*Theme, error) { func (e *Exercice) GetTheme() (*Theme, error) {
t := &Theme{} t := &Theme{}
if err := DBQueryRow("SELECT id_theme, name, locked, url_id, path, authors, intro, headline, image, partner_img, partner_href, partner_text FROM themes WHERE id_theme=?", e.IdTheme).Scan(&t.Id, &t.Name, &t.Locked, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.PartnerImage, &t.PartnerLink, &t.PartnerText); err != nil { if err := DBQueryRow("SELECT id_theme, name, locked, url_id, path, authors, intro, headline, image, background_color, partner_img, partner_href, partner_text FROM themes WHERE id_theme=?", e.IdTheme).Scan(&t.Id, &t.Name, &t.Locked, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.BackgroundColor, &t.PartnerImage, &t.PartnerLink, &t.PartnerText); err != nil {
return t, err return t, err
} }
@ -78,7 +79,7 @@ func (e *Exercice) GetTheme() (*Theme, error) {
// GetThemeByName retrieves a Theme from its title // GetThemeByName retrieves a Theme from its title
func GetThemeByName(name string) (*Theme, error) { func GetThemeByName(name string) (*Theme, error) {
t := &Theme{} t := &Theme{}
if err := DBQueryRow("SELECT id_theme, name, locked, url_id, path, authors, intro, headline, image, partner_img, partner_text FROM themes WHERE name=?", name).Scan(&t.Id, &t.Name, &t.Locked, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.PartnerImage, &t.PartnerText); err != nil { if err := DBQueryRow("SELECT id_theme, name, locked, url_id, path, authors, intro, headline, image, background_color, partner_img, partner_text FROM themes WHERE name=?", name).Scan(&t.Id, &t.Name, &t.Locked, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.BackgroundColor, &t.PartnerImage, &t.PartnerText); err != nil {
return t, err return t, err
} }
@ -88,14 +89,14 @@ func GetThemeByName(name string) (*Theme, error) {
// GetThemeByPath retrieves a Theme from its dirname // GetThemeByPath retrieves a Theme from its dirname
func GetThemeByPath(dirname string) (*Theme, error) { func GetThemeByPath(dirname string) (*Theme, error) {
t := &Theme{} t := &Theme{}
err := DBQueryRow("SELECT id_theme, name, locked, url_id, path, authors, intro, headline, image, partner_img, partner_href, partner_text FROM themes WHERE path=?", dirname).Scan(&t.Id, &t.Name, &t.Locked, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.PartnerImage, &t.PartnerLink, &t.PartnerText) err := DBQueryRow("SELECT id_theme, name, locked, url_id, path, authors, intro, headline, image, background_color, partner_img, partner_href, partner_text FROM themes WHERE path=?", dirname).Scan(&t.Id, &t.Name, &t.Locked, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.BackgroundColor, &t.PartnerImage, &t.PartnerLink, &t.PartnerText)
return t, err return t, err
} }
// CreateTheme creates and fills a new struct Theme and registers it into the database. // CreateTheme creates and fills a new struct Theme and registers it into the database.
func CreateTheme(theme *Theme) (*Theme, error) { func CreateTheme(theme *Theme) (*Theme, error) {
if res, err := DBExec("INSERT INTO themes (name, locked, url_id, authors, path, intro, headline, image, partner_img, partner_href, partner_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", theme.Name, theme.Locked, theme.URLId, theme.Authors, theme.Path, theme.Intro, theme.Headline, theme.Image, theme.PartnerImage, theme.PartnerLink, theme.PartnerText); err != nil { if res, err := DBExec("INSERT INTO themes (name, locked, url_id, authors, path, intro, headline, image, background_color, partner_img, partner_href, partner_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", theme.Name, theme.Locked, theme.URLId, theme.Authors, theme.Path, theme.Intro, theme.Headline, theme.Image, theme.BackgroundColor, theme.PartnerImage, theme.PartnerLink, theme.PartnerText); err != nil {
return nil, err return nil, err
} else if theme.Id, err = res.LastInsertId(); err != nil { } else if theme.Id, err = res.LastInsertId(); err != nil {
return nil, err return nil, err
@ -116,7 +117,7 @@ func (t *Theme) FixURLId() bool {
// Update applies modifications back to the database. // Update applies modifications back to the database.
func (t *Theme) Update() (int64, error) { func (t *Theme) Update() (int64, error) {
if res, err := DBExec("UPDATE themes SET name = ?, locked = ?, url_id = ?, authors = ?, path = ?, intro = ?, headline = ?, image = ?, partner_img = ?, partner_href = ?, partner_text = ? WHERE id_theme = ?", t.Name, t.Locked, t.URLId, t.Authors, t.Path, t.Intro, t.Headline, t.Image, t.PartnerImage, t.PartnerLink, t.PartnerText, t.Id); err != nil { if res, err := DBExec("UPDATE themes SET name = ?, locked = ?, url_id = ?, authors = ?, path = ?, intro = ?, headline = ?, image = ?, background_color = ?, partner_img = ?, partner_href = ?, partner_text = ? WHERE id_theme = ?", t.Name, t.Locked, t.URLId, t.Authors, t.Path, t.Intro, t.Headline, t.Image, t.BackgroundColor, t.PartnerImage, t.PartnerLink, t.PartnerText, t.Id); err != nil {
return 0, err return 0, err
} else if nb, err := res.RowsAffected(); err != nil { } else if nb, err := res.RowsAffected(); err != nil {
return 0, err return 0, err

View File

@ -10,32 +10,34 @@ var GlobalScoreCoefficient float64 = 1
// exportedExercice is a structure representing a challenge, as exposed to players. // exportedExercice is a structure representing a challenge, as exposed to players.
type exportedExercice struct { type exportedExercice struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Title string `json:"title"` Title string `json:"title"`
Authors string `json:"authors,omitempty"` Authors string `json:"authors,omitempty"`
Headline string `json:"headline,omitempty"` Headline string `json:"headline,omitempty"`
Image string `json:"image,omitempty"` Image string `json:"image,omitempty"`
URLId string `json:"urlid"` BackgroundColor string `json:"background_color,omitempty"`
Tags []string `json:"tags"` URLId string `json:"urlid"`
Gain int64 `json:"gain"` Tags []string `json:"tags"`
Coeff float64 `json:"curcoeff"` Gain int64 `json:"gain"`
Solved int64 `json:"solved"` Coeff float64 `json:"curcoeff"`
Tried int64 `json:"tried"` Solved int64 `json:"solved"`
Tried int64 `json:"tried"`
} }
// exportedTheme is a structure representing a Theme, as exposed to players. // exportedTheme is a structure representing a Theme, as exposed to players.
type exportedTheme struct { type exportedTheme struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
URLId string `json:"urlid"` URLId string `json:"urlid"`
Locked bool `json:"locked,omitempty"` Locked bool `json:"locked,omitempty"`
Authors string `json:"authors,omitempty"` Authors string `json:"authors,omitempty"`
Headline string `json:"headline,omitempty"` Headline string `json:"headline,omitempty"`
Intro string `json:"intro,omitempty"` Intro string `json:"intro,omitempty"`
Image string `json:"image,omitempty"` Image string `json:"image,omitempty"`
PartnerImage string `json:"partner_img,omitempty"` BackgroundColor string `json:"background_color,omitempty"`
PartnerLink string `json:"partner_href,omitempty"` PartnerImage string `json:"partner_img,omitempty"`
PartnerText string `json:"partner_txt,omitempty"` PartnerLink string `json:"partner_href,omitempty"`
Exercices []exportedExercice `json:"exercices"` PartnerText string `json:"partner_txt,omitempty"`
Exercices []exportedExercice `json:"exercices"`
} }
// Exportedthemes exports themes from the database, to be displayed to players. // Exportedthemes exports themes from the database, to be displayed to players.
@ -69,6 +71,10 @@ func ExportThemes() (interface{}, error) {
if len(exercice.Image) > 0 { if len(exercice.Image) > 0 {
exoimgpath = path.Join(FilesDir, exercice.Image) exoimgpath = path.Join(FilesDir, exercice.Image)
} }
exobackgroundcolor := ""
if exercice.BackgroundColor > 0 || len(exercice.Image) > 0 {
exobackgroundcolor = fmt.Sprintf("#%06X", exercice.BackgroundColor)
}
tags, _ := exercice.GetTags() tags, _ := exercice.GetTags()
exos = append(exos, exportedExercice{ exos = append(exos, exportedExercice{
@ -77,6 +83,7 @@ func ExportThemes() (interface{}, error) {
exercice.Authors, exercice.Authors,
exercice.Headline, exercice.Headline,
exoimgpath, exoimgpath,
exobackgroundcolor,
exercice.URLId, exercice.URLId,
tags, tags,
int64(float64(exercice.Gain) * GlobalScoreCoefficient), int64(float64(exercice.Gain) * GlobalScoreCoefficient),
@ -92,6 +99,11 @@ func ExportThemes() (interface{}, error) {
imgpath = path.Join(FilesDir, theme.Image) imgpath = path.Join(FilesDir, theme.Image)
} }
thmbackgroundcolor := ""
if theme.BackgroundColor > 0 || len(theme.Image) > 0 {
thmbackgroundcolor = fmt.Sprintf("#%06X", theme.BackgroundColor)
}
partnerImgpath := "" partnerImgpath := ""
if len(theme.PartnerImage) > 0 { if len(theme.PartnerImage) > 0 {
partnerImgpath = path.Join(FilesDir, theme.PartnerImage) partnerImgpath = path.Join(FilesDir, theme.PartnerImage)
@ -105,6 +117,7 @@ func ExportThemes() (interface{}, error) {
theme.Headline, theme.Headline,
strings.Replace(theme.Intro, "$FILES$", FilesDir, -1), strings.Replace(theme.Intro, "$FILES$", FilesDir, -1),
imgpath, imgpath,
thmbackgroundcolor,
partnerImgpath, partnerImgpath,
theme.PartnerLink, theme.PartnerLink,
theme.PartnerText, theme.PartnerText,