├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── h2-1.4.190.jar ├── settings.gradle └── src └── main ├── java └── com │ └── teamtreehouse │ └── giflib │ ├── Application.java │ ├── config │ ├── AppConfig.java │ ├── DataConfig.java │ └── TemplateConfig.java │ ├── dao │ ├── CategoryDao.java │ ├── CategoryDaoImpl.java │ ├── GifDao.java │ └── GifDaoImpl.java │ ├── model │ ├── Category.java │ └── Gif.java │ ├── service │ ├── CategoryService.java │ ├── CategoryServiceImpl.java │ ├── GifService.java │ └── GifServiceImpl.java │ └── web │ ├── Color.java │ ├── FlashMessage.java │ └── controller │ ├── CategoryController.java │ └── GifController.java └── resources ├── app.properties ├── hibernate.cfg.xml ├── static ├── app.css ├── app.js ├── favicon.png ├── icons │ ├── favorite.png │ └── unfavorite.png ├── images │ └── me.jpg └── vendor │ ├── codrops │ ├── css │ │ ├── cs-select.css │ │ └── cs-skin-border.css │ ├── fonts │ │ ├── codropsicons │ │ │ ├── codropsicons.eot │ │ │ ├── codropsicons.svg │ │ │ ├── codropsicons.ttf │ │ │ ├── codropsicons.woff │ │ │ └── license.txt │ │ └── icomoon │ │ │ ├── icomoon.eot │ │ │ ├── icomoon.svg │ │ │ ├── icomoon.ttf │ │ │ └── icomoon.woff │ └── js │ │ ├── classie.js │ │ └── selectFx.js │ ├── jquery │ └── jquery-1.11.3.js │ └── materialize │ ├── LICENSE │ ├── README.md │ ├── css │ ├── materialize.css │ └── materialize.min.css │ ├── font │ ├── material-design-icons │ │ ├── LICENSE.txt │ │ ├── Material-Design-Icons.eot │ │ ├── Material-Design-Icons.svg │ │ ├── Material-Design-Icons.ttf │ │ ├── Material-Design-Icons.woff │ │ └── Material-Design-Icons.woff2 │ └── roboto │ │ ├── Roboto-Bold.ttf │ │ ├── Roboto-Bold.woff │ │ ├── Roboto-Bold.woff2 │ │ ├── Roboto-Light.ttf │ │ ├── Roboto-Light.woff │ │ ├── Roboto-Light.woff2 │ │ ├── Roboto-Medium.ttf │ │ ├── Roboto-Medium.woff │ │ ├── Roboto-Medium.woff2 │ │ ├── Roboto-Regular.ttf │ │ ├── Roboto-Regular.woff │ │ ├── Roboto-Regular.woff2 │ │ ├── Roboto-Thin.ttf │ │ ├── Roboto-Thin.woff │ │ └── Roboto-Thin.woff2 │ └── js │ ├── materialize.js │ └── materialize.min.js └── templates ├── category ├── details.html ├── form.html └── index.html ├── error.html ├── gif ├── details.html ├── favorites.html ├── form.html └── index.html └── layout.html /.gitignore: -------------------------------------------------------------------------------- 1 | ## IntelliJ 2 | *.iml 3 | .idea/ 4 | /out/ 5 | /build/ 6 | 7 | ## Gradle 8 | .gradle/ 9 | 10 | ## Package Files # 11 | *.jar 12 | !h2*.jar 13 | *.war 14 | *.ear -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gif.lib 2 | This application is used in the Spring with Hibernate Treehouse course. The app was originally built in the Spring Basics course, and its functionality is extended in the following ways: 3 | 4 | - Adds persisted data, using an H2 database 5 | - Manages data with Hibernate 6 | 7 | What this app does: 8 | 9 | - Serves dynamic web content according to URI, including index and detail pages for categories and GIFs 10 | - Includes database connectivity, where GIF data is stored 11 | - Allows a user to perform CRUD (create, read, update, delete) operations on GIF and category data 12 | - Performs server-side form validation for adding/updating GIFs and categories 13 | - Uses a database 14 | - Serves static assets, such as images, fonts, CSS, and JS 15 | 16 | What this app does **NOT** do: 17 | 18 | - Implement user authentication -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.teamtreehouse' 2 | version '1.0-SNAPSHOT' 3 | 4 | // Include the Spring Boot plugin 5 | buildscript { 6 | repositories{ 7 | mavenCentral() 8 | } 9 | dependencies { 10 | classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.3.3.RELEASE' 11 | } 12 | } 13 | 14 | // Apply the Spring Boot plugin 15 | apply plugin: 'spring-boot' 16 | 17 | // Apply the Java plugin (expects src/main/java to be source folder) 18 | apply plugin: 'java' 19 | 20 | // Specify the location where our dependencies will be found 21 | repositories { 22 | mavenCentral() 23 | } 24 | 25 | // Specify dependencies 26 | dependencies { 27 | compile 'org.springframework.boot:spring-boot-starter-thymeleaf' 28 | compile 'org.hashids:hashids:1.0.1' 29 | compile 'org.springframework:spring-orm:4.2.5.RELEASE' 30 | compile 'org.hibernate:hibernate-core:5.2.1.Final' 31 | compile 'org.apache.tomcat:tomcat-dbcp:8.0.32' 32 | compile 'com.h2database:h2:1.4.191' 33 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Feb 26 15:28:13 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:description=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /h2-1.4.190.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/h2-1.4.190.jar -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'giflib-hibernate' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/Application.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.context.annotation.ComponentScan; 6 | 7 | @EnableAutoConfiguration 8 | @ComponentScan 9 | public class Application { 10 | public static void main(String[] args) { 11 | SpringApplication.run(Application.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/config/AppConfig.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.config; 2 | 3 | import org.hashids.Hashids; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.context.annotation.PropertySource; 8 | import org.springframework.core.env.Environment; 9 | 10 | 11 | @Configuration 12 | @PropertySource("app.properties") 13 | public class AppConfig { 14 | @Autowired 15 | private Environment env; 16 | 17 | @Bean 18 | public Hashids hashids() { 19 | return new Hashids(env.getProperty("giflib.hash.salt"),8); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/config/DataConfig.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.config; 2 | 3 | import org.apache.tomcat.dbcp.dbcp2.BasicDataSource; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.context.annotation.PropertySource; 8 | import org.springframework.core.env.Environment; 9 | import org.springframework.core.io.ClassPathResource; 10 | import org.springframework.core.io.Resource; 11 | import org.springframework.orm.hibernate5.LocalSessionFactoryBean; 12 | 13 | import javax.sql.DataSource; 14 | 15 | @Configuration 16 | @PropertySource("app.properties") 17 | public class DataConfig { 18 | @Autowired 19 | private Environment env; 20 | 21 | @Bean 22 | public LocalSessionFactoryBean sessionFactory() { 23 | Resource config = new ClassPathResource("hibernate.cfg.xml"); 24 | LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean(); 25 | sessionFactory.setConfigLocation(config); 26 | sessionFactory.setPackagesToScan(env.getProperty("giflib.entity.package")); 27 | sessionFactory.setDataSource(dataSource()); 28 | return sessionFactory; 29 | } 30 | 31 | @Bean 32 | public DataSource dataSource() { 33 | BasicDataSource ds = new BasicDataSource(); 34 | 35 | // Driver class name 36 | ds.setDriverClassName(env.getProperty("giflib.db.driver")); 37 | 38 | // Set URL 39 | ds.setUrl(env.getProperty("giflib.db.url")); 40 | 41 | // Set username & password 42 | ds.setUsername(env.getProperty("giflib.db.username")); 43 | ds.setPassword(env.getProperty("giflib.db.password")); 44 | 45 | return ds; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/config/TemplateConfig.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.thymeleaf.spring4.SpringTemplateEngine; 6 | import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver; 7 | import org.thymeleaf.spring4.view.ThymeleafViewResolver; 8 | 9 | @Configuration 10 | public class TemplateConfig { 11 | @Bean 12 | public SpringResourceTemplateResolver templateResolver() { 13 | final SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); 14 | templateResolver.setCacheable(false); 15 | templateResolver.setPrefix("classpath:/templates/"); 16 | templateResolver.setSuffix(".html"); 17 | return templateResolver; 18 | } 19 | 20 | @Bean 21 | public SpringTemplateEngine templateEngine() { 22 | final SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine(); 23 | springTemplateEngine.addTemplateResolver(templateResolver()); 24 | return springTemplateEngine; 25 | } 26 | 27 | @Bean 28 | public ThymeleafViewResolver viewResolver() { 29 | final ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); 30 | viewResolver.setTemplateEngine(templateEngine()); 31 | viewResolver.setOrder(1); 32 | return viewResolver; 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/dao/CategoryDao.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.dao; 2 | 3 | import com.teamtreehouse.giflib.model.Category; 4 | 5 | import java.util.List; 6 | 7 | public interface CategoryDao { 8 | List findAll(); 9 | Category findById(Long id); 10 | void save(Category category); 11 | void delete(Category category); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/dao/CategoryDaoImpl.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.dao; 2 | 3 | import com.teamtreehouse.giflib.model.Category; 4 | import org.hibernate.Hibernate; 5 | import org.hibernate.Session; 6 | import org.hibernate.SessionFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import javax.persistence.criteria.CriteriaBuilder; 11 | import javax.persistence.criteria.CriteriaQuery; 12 | import java.util.List; 13 | 14 | @Repository 15 | public class CategoryDaoImpl implements CategoryDao { 16 | @Autowired 17 | private SessionFactory sessionFactory; 18 | 19 | @Override 20 | @SuppressWarnings("unchecked") 21 | public List findAll() { 22 | // Open a session 23 | Session session = sessionFactory.openSession(); 24 | 25 | // DEPRECATED as of Hibernate 5.2.0 26 | // List categories = session.createCriteria(Category.class).list(); 27 | 28 | // Create CriteriaBuilder 29 | CriteriaBuilder builder = session.getCriteriaBuilder(); 30 | 31 | // Create CriteriaQuery 32 | CriteriaQuery criteria = builder.createQuery(Category.class); 33 | 34 | // Specify criteria root 35 | criteria.from(Category.class); 36 | 37 | // Execute query 38 | List categories = session.createQuery(criteria).getResultList(); 39 | 40 | // Close session 41 | session.close(); 42 | 43 | return categories; 44 | } 45 | 46 | @Override 47 | public Category findById(Long id) { 48 | Session session = sessionFactory.openSession(); 49 | Category category = session.get(Category.class,id); 50 | Hibernate.initialize(category.getGifs()); 51 | session.close(); 52 | return category; 53 | } 54 | 55 | @Override 56 | public void save(Category category) { 57 | // Open a session 58 | Session session = sessionFactory.openSession(); 59 | 60 | // Begin a transaction 61 | session.beginTransaction(); 62 | 63 | // Save the category 64 | session.saveOrUpdate(category); 65 | 66 | // Commit the transaction 67 | session.getTransaction().commit(); 68 | 69 | // Close the session 70 | session.close(); 71 | } 72 | 73 | @Override 74 | public void delete(Category category) { 75 | Session session = sessionFactory.openSession(); 76 | session.beginTransaction(); 77 | session.delete(category); 78 | session.getTransaction().commit(); 79 | session.close(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/dao/GifDao.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.dao; 2 | 3 | import com.teamtreehouse.giflib.model.Gif; 4 | 5 | import java.util.List; 6 | 7 | public interface GifDao { 8 | List findAll(); 9 | Gif findById(Long id); 10 | void save(Gif gif); 11 | void delete(Gif gif); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/dao/GifDaoImpl.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.dao; 2 | 3 | import com.teamtreehouse.giflib.model.Gif; 4 | import org.hibernate.Session; 5 | import org.hibernate.SessionFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import javax.persistence.criteria.CriteriaBuilder; 10 | import javax.persistence.criteria.CriteriaQuery; 11 | import java.util.List; 12 | 13 | @Repository 14 | public class GifDaoImpl implements GifDao { 15 | @Autowired 16 | private SessionFactory sessionFactory; 17 | 18 | @Override 19 | @SuppressWarnings("unchecked") 20 | public List findAll() { 21 | Session session = sessionFactory.openSession(); 22 | 23 | // DEPRECATED as of Hibernate 5.2.0 24 | // List gifs = session.createCriteria(Gif.class).list(); 25 | 26 | // Create CriteriaBuilder 27 | CriteriaBuilder builder = session.getCriteriaBuilder(); 28 | 29 | // Create CriteriaQuery 30 | CriteriaQuery criteria = builder.createQuery(Gif.class); 31 | 32 | // Specify criteria root 33 | criteria.from(Gif.class); 34 | 35 | // Execute query 36 | List gifs = session.createQuery(criteria).getResultList(); 37 | 38 | session.close(); 39 | return gifs; 40 | } 41 | 42 | @Override 43 | public Gif findById(Long id) { 44 | Session session = sessionFactory.openSession(); 45 | Gif gif = session.get(Gif.class,id); 46 | session.close(); 47 | return gif; 48 | } 49 | 50 | @Override 51 | public void save(Gif gif) { 52 | Session session = sessionFactory.openSession(); 53 | session.beginTransaction(); 54 | session.saveOrUpdate(gif); 55 | session.getTransaction().commit(); 56 | session.close(); 57 | } 58 | 59 | @Override 60 | public void delete(Gif gif) { 61 | Session session = sessionFactory.openSession(); 62 | session.beginTransaction(); 63 | session.delete(gif); 64 | session.getTransaction().commit(); 65 | session.close(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/model/Category.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.model; 2 | 3 | import javax.persistence.*; 4 | import javax.validation.constraints.NotNull; 5 | import javax.validation.constraints.Pattern; 6 | import javax.validation.constraints.Size; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Entity 11 | public class Category { 12 | @Id 13 | @GeneratedValue(strategy = GenerationType.IDENTITY) 14 | private Long id; 15 | 16 | @NotNull 17 | @Size(min = 3, max = 12) 18 | private String name; 19 | 20 | @NotNull 21 | @Pattern(regexp = "#[0-9a-fA-F]{6}") 22 | private String colorCode; 23 | 24 | @OneToMany(mappedBy = "category") 25 | private List gifs = new ArrayList<>(); 26 | 27 | public Category(){} 28 | 29 | public Long getId() { 30 | return id; 31 | } 32 | 33 | public void setId(Long id) { 34 | this.id = id; 35 | } 36 | 37 | public String getName() { 38 | return name; 39 | } 40 | 41 | public void setName(String name) { 42 | this.name = name; 43 | } 44 | 45 | public String getColorCode() { 46 | return colorCode; 47 | } 48 | 49 | public void setColorCode(String colorCode) { 50 | this.colorCode = colorCode; 51 | } 52 | 53 | public List getGifs() { 54 | return gifs; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/model/Gif.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.model; 2 | 3 | import javax.persistence.*; 4 | import java.time.LocalDateTime; 5 | import java.time.temporal.ChronoUnit; 6 | 7 | @Entity 8 | public class Gif { 9 | @Id 10 | @GeneratedValue(strategy = GenerationType.IDENTITY) 11 | private Long id; 12 | 13 | @Lob 14 | private byte[] bytes; 15 | private String description; 16 | 17 | @ManyToOne 18 | private Category category; 19 | private LocalDateTime dateUploaded = LocalDateTime.now(); 20 | private String username = "You"; 21 | private boolean favorite; 22 | private String hash; 23 | 24 | public Gif(){} 25 | 26 | public String getTimeSinceUploaded() { 27 | String unit = ""; 28 | LocalDateTime now = LocalDateTime.now(); 29 | long diff; 30 | if((diff = ChronoUnit.SECONDS.between(dateUploaded,now)) < 60){ 31 | unit = "secs"; 32 | } else if ((diff = ChronoUnit.MINUTES.between(dateUploaded,now)) < 60) { 33 | unit = "mins"; 34 | } else if ((diff = ChronoUnit.HOURS.between(dateUploaded,now)) < 24) { 35 | unit = "hours"; 36 | } else if ((diff = ChronoUnit.DAYS.between(dateUploaded,now)) < 30) { 37 | unit = "days"; 38 | } else if ((diff = ChronoUnit.MONTHS.between(dateUploaded,now)) < 12) { 39 | unit = "months"; 40 | } else{ 41 | diff = ChronoUnit.YEARS.between(dateUploaded,now); 42 | } 43 | return String.format("%d %s",diff,unit); 44 | } 45 | 46 | public Long getId() { 47 | return id; 48 | } 49 | 50 | public void setId(Long id) { 51 | this.id = id; 52 | } 53 | 54 | public byte[] getBytes() { 55 | return bytes; 56 | } 57 | 58 | public void setBytes(byte[] bytes) { 59 | this.bytes = bytes; 60 | } 61 | 62 | public String getDescription() { 63 | return description; 64 | } 65 | 66 | public void setDescription(String description) { 67 | this.description = description; 68 | } 69 | 70 | public Category getCategory() { 71 | return category; 72 | } 73 | 74 | public void setCategory(Category category) { 75 | this.category = category; 76 | } 77 | 78 | public LocalDateTime getDateUploaded() { 79 | return dateUploaded; 80 | } 81 | 82 | public void setDateUploaded(LocalDateTime dateUploaded) { 83 | this.dateUploaded = dateUploaded; 84 | } 85 | 86 | public String getUsername() { 87 | return username; 88 | } 89 | 90 | public void setUsername(String username) { 91 | this.username = username; 92 | } 93 | 94 | public boolean isFavorite() { 95 | return favorite; 96 | } 97 | 98 | public void setFavorite(boolean favorite) { 99 | this.favorite = favorite; 100 | } 101 | 102 | public String getHash() { 103 | return hash; 104 | } 105 | 106 | public void setHash(String hash) { 107 | this.hash = hash; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/service/CategoryService.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.service; 2 | 3 | import com.teamtreehouse.giflib.model.Category; 4 | 5 | import java.util.List; 6 | 7 | public interface CategoryService { 8 | List findAll(); 9 | Category findById(Long id); 10 | void save(Category category); 11 | void delete(Category category); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/service/CategoryServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.service; 2 | 3 | import com.teamtreehouse.giflib.dao.CategoryDao; 4 | import com.teamtreehouse.giflib.model.Category; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.List; 9 | 10 | @Service 11 | public class CategoryServiceImpl implements CategoryService { 12 | @Autowired 13 | private CategoryDao categoryDao; 14 | 15 | @Override 16 | public List findAll() { 17 | return categoryDao.findAll(); 18 | } 19 | 20 | @Override 21 | public Category findById(Long id) { 22 | return categoryDao.findById(id); 23 | } 24 | 25 | @Override 26 | public void save(Category category) { 27 | categoryDao.save(category); 28 | } 29 | 30 | @Override 31 | public void delete(Category category) { 32 | categoryDao.delete(category); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/service/GifService.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.service; 2 | 3 | import com.teamtreehouse.giflib.model.Gif; 4 | import org.springframework.web.multipart.MultipartFile; 5 | 6 | import java.util.List; 7 | 8 | public interface GifService { 9 | List findAll(); 10 | Gif findById(Long id); 11 | void save(Gif gif, MultipartFile file); 12 | void delete(Gif gif); 13 | void toggleFavorite(Gif gif); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/service/GifServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.service; 2 | 3 | import com.teamtreehouse.giflib.dao.GifDao; 4 | import com.teamtreehouse.giflib.model.Gif; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.web.multipart.MultipartFile; 8 | 9 | import java.io.IOException; 10 | import java.util.List; 11 | 12 | @Service 13 | public class GifServiceImpl implements GifService { 14 | @Autowired 15 | private GifDao gifDao; 16 | 17 | @Override 18 | public List findAll() { 19 | return gifDao.findAll(); 20 | } 21 | 22 | @Override 23 | public Gif findById(Long id) { 24 | return gifDao.findById(id); 25 | } 26 | 27 | @Override 28 | public void save(Gif gif, MultipartFile file) { 29 | try { 30 | gif.setBytes(file.getBytes()); 31 | gifDao.save(gif); 32 | } catch (IOException e) { 33 | System.err.println("Unable to get byte array from uploaded file."); 34 | } 35 | } 36 | 37 | @Override 38 | public void delete(Gif gif) { 39 | gifDao.delete(gif); 40 | } 41 | 42 | @Override 43 | public void toggleFavorite(Gif gif) { 44 | gif.setFavorite(!gif.isFavorite()); 45 | gifDao.save(gif); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/web/Color.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.web; 2 | 3 | public enum Color { 4 | AQUA("Aqua","#59b3b3"), 5 | BLUE("Blue","#5976b3"), 6 | PURPLE("Purple","#7e59b3"), 7 | FUCHSIA("Fucshia","#b35986"), 8 | ORANGE("Orange","#b36859"), 9 | GOLD("Gold","#b38f59"); 10 | 11 | private final String name; 12 | private final String hexCode; 13 | 14 | Color(String name, String hexCode) { 15 | this.name = name; 16 | this.hexCode = hexCode; 17 | } 18 | 19 | public String getName() { 20 | return name; 21 | } 22 | 23 | public String getHexCode() { 24 | return hexCode; 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/web/FlashMessage.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.web; 2 | 3 | public class FlashMessage { 4 | private String message; 5 | private Status status; 6 | 7 | public FlashMessage(String message, Status status) { 8 | this.message = message; 9 | this.status = status; 10 | } 11 | 12 | public String getMessage() { 13 | return message; 14 | } 15 | 16 | public Status getStatus() { 17 | return status; 18 | } 19 | 20 | public static enum Status { 21 | SUCCESS, FAILURE 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/web/controller/CategoryController.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.web.controller; 2 | 3 | import com.teamtreehouse.giflib.model.Category; 4 | import com.teamtreehouse.giflib.service.CategoryService; 5 | import com.teamtreehouse.giflib.web.Color; 6 | import com.teamtreehouse.giflib.web.FlashMessage; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.validation.BindingResult; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestMethod; 14 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 15 | 16 | import javax.validation.Valid; 17 | import java.util.List; 18 | 19 | @Controller 20 | public class CategoryController { 21 | @Autowired 22 | private CategoryService categoryService; 23 | 24 | // Index of all categories 25 | @SuppressWarnings("unchecked") 26 | @RequestMapping("/categories") 27 | public String listCategories(Model model) { 28 | // TODO: Get all categories 29 | List categories = categoryService.findAll(); 30 | 31 | model.addAttribute("categories",categories); 32 | return "category/index"; 33 | } 34 | 35 | // Single category page 36 | @RequestMapping("/categories/{categoryId}") 37 | public String category(@PathVariable Long categoryId, Model model) { 38 | // TODO: Get the category given by categoryId 39 | Category category = null; 40 | 41 | model.addAttribute("category", category); 42 | return "category/details"; 43 | } 44 | 45 | // Form for adding a new category 46 | @RequestMapping("categories/add") 47 | public String formNewCategory(Model model) { 48 | // TODO: Add model attributes needed for new form 49 | if(!model.containsAttribute("category")) { 50 | model.addAttribute("category",new Category()); 51 | } 52 | model.addAttribute("colors", Color.values()); 53 | model.addAttribute("action","/categories"); 54 | model.addAttribute("heading","New Category"); 55 | model.addAttribute("submit","Add"); 56 | 57 | return "category/form"; 58 | } 59 | 60 | // Form for editing an existing category 61 | @RequestMapping("categories/{categoryId}/edit") 62 | public String formEditCategory(@PathVariable Long categoryId, Model model) { 63 | // TODO: Add model attributes needed for new form 64 | if(!model.containsAttribute("category")) { 65 | model.addAttribute("category",categoryService.findById(categoryId)); 66 | } 67 | model.addAttribute("colors", Color.values()); 68 | model.addAttribute("action",String.format("/categories/%s",categoryId)); 69 | model.addAttribute("heading","Edit Category"); 70 | model.addAttribute("submit","Update"); 71 | 72 | return "category/form"; 73 | } 74 | 75 | // Update an existing category 76 | @RequestMapping(value = "/categories/{categoryId}", method = RequestMethod.POST) 77 | public String updateCategory(@Valid Category category, BindingResult result, RedirectAttributes redirectAttributes) { 78 | // TODO: Update category if valid data was received 79 | if(result.hasErrors()) { 80 | // Include validation errors upon redirect 81 | redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.category",result); 82 | 83 | // Add category if invalid was received 84 | redirectAttributes.addFlashAttribute("category",category); 85 | 86 | // Redirect back to the form 87 | return String.format("redirect:/categories/%s/edit",category.getId()); 88 | } 89 | 90 | categoryService.save(category); 91 | 92 | redirectAttributes.addFlashAttribute("flash",new FlashMessage("Category successfully updated!", FlashMessage.Status.SUCCESS)); 93 | 94 | // TODO: Redirect browser to /categories 95 | return "redirect:/categories"; 96 | } 97 | 98 | // Add a category 99 | @RequestMapping(value = "/categories", method = RequestMethod.POST) 100 | public String addCategory(@Valid Category category, BindingResult result, RedirectAttributes redirectAttributes) { 101 | // TODO: Add category if valid data was received 102 | if(result.hasErrors()) { 103 | // Include validation errors upon redirect 104 | redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.category",result); 105 | 106 | // Add category if invalid was received 107 | redirectAttributes.addFlashAttribute("category",category); 108 | 109 | // Redirect back to the form 110 | return "redirect:/categories/add"; 111 | } 112 | 113 | categoryService.save(category); 114 | 115 | redirectAttributes.addFlashAttribute("flash",new FlashMessage("Category successfully added!", FlashMessage.Status.SUCCESS)); 116 | 117 | // TODO: Redirect browser to /categories 118 | return "redirect:/categories"; 119 | } 120 | 121 | // Delete an existing category 122 | @RequestMapping(value = "/categories/{categoryId}/delete", method = RequestMethod.POST) 123 | public String deleteCategory(@PathVariable Long categoryId, RedirectAttributes redirectAttributes) { 124 | Category cat = categoryService.findById(categoryId); 125 | 126 | // TODO: Delete category if it contains no GIFs 127 | if(cat.getGifs().size() > 0) { 128 | redirectAttributes.addFlashAttribute("flash",new FlashMessage("Only empty categories can be deleted.", FlashMessage.Status.FAILURE)); 129 | return String.format("redirect:/categories/%s/edit",categoryId); 130 | } 131 | categoryService.delete(cat); 132 | redirectAttributes.addFlashAttribute("flash",new FlashMessage("Category deleted!", FlashMessage.Status.SUCCESS)); 133 | 134 | // TODO: Redirect browser to /categories 135 | return "redirect:/categories"; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/com/teamtreehouse/giflib/web/controller/GifController.java: -------------------------------------------------------------------------------- 1 | package com.teamtreehouse.giflib.web.controller; 2 | 3 | import com.teamtreehouse.giflib.model.Gif; 4 | import com.teamtreehouse.giflib.service.CategoryService; 5 | import com.teamtreehouse.giflib.service.GifService; 6 | import com.teamtreehouse.giflib.web.FlashMessage; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.web.bind.annotation.*; 11 | import org.springframework.web.multipart.MultipartFile; 12 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 13 | 14 | import javax.servlet.http.HttpServletRequest; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | @Controller 19 | public class GifController { 20 | @Autowired 21 | private GifService gifService; 22 | 23 | @Autowired 24 | private CategoryService categoryService; 25 | 26 | // Home page - index of all GIFs 27 | @RequestMapping("/") 28 | public String listGifs(Model model) { 29 | // TODO: Get all gifs 30 | List gifs = gifService.findAll(); 31 | 32 | model.addAttribute("gifs", gifs); 33 | return "gif/index"; 34 | } 35 | 36 | // Single GIF page 37 | @RequestMapping("/gifs/{gifId}") 38 | public String gifDetails(@PathVariable Long gifId, Model model) { 39 | // TODO: Get gif whose id is gifId 40 | Gif gif = gifService.findById(gifId); 41 | 42 | model.addAttribute("gif", gif); 43 | return "gif/details"; 44 | } 45 | 46 | // GIF image data 47 | @RequestMapping("/gifs/{gifId}.gif") 48 | @ResponseBody 49 | public byte[] gifImage(@PathVariable Long gifId) { 50 | // TODO: Return image data as byte array of the GIF whose id is gifId 51 | return gifService.findById(gifId).getBytes(); 52 | } 53 | 54 | // Favorites - index of all GIFs marked favorite 55 | @RequestMapping("/favorites") 56 | public String favorites(Model model) { 57 | // TODO: Get list of all GIFs marked as favorite 58 | List faves = new ArrayList<>(); 59 | 60 | model.addAttribute("gifs",faves); 61 | model.addAttribute("username","Chris Ramacciotti"); // Static username 62 | return "gif/favorites"; 63 | } 64 | 65 | // Upload a new GIF 66 | @RequestMapping(value = "/gifs", method = RequestMethod.POST) 67 | public String addGif(Gif gif, @RequestParam MultipartFile file, RedirectAttributes redirectAttributes) { 68 | // TODO: Upload new GIF if data is valid 69 | gifService.save(gif,file); 70 | 71 | // Add flash message for success 72 | redirectAttributes.addFlashAttribute("flash",new FlashMessage("GIF successfully uploaded!", FlashMessage.Status.SUCCESS)); 73 | 74 | // TODO: Redirect browser to new GIF's detail view 75 | return String.format("redirect:/gifs/%s",gif.getId()); 76 | } 77 | 78 | // Form for uploading a new GIF 79 | @RequestMapping("/upload") 80 | public String formNewGif(Model model) { 81 | // TODO: Add model attributes needed for new GIF upload form 82 | if(!model.containsAttribute("gif")) { 83 | model.addAttribute("gif",new Gif()); 84 | } 85 | model.addAttribute("categories",categoryService.findAll()); 86 | model.addAttribute("action","/gifs"); 87 | model.addAttribute("heading","Upload"); 88 | model.addAttribute("submit","Add"); 89 | 90 | return "gif/form"; 91 | } 92 | 93 | // Form for editing an existing GIF 94 | @RequestMapping(value = "/gifs/{gifId}/edit") 95 | public String formEditGif(@PathVariable Long gifId, Model model) { 96 | // TODO: Add model attributes needed for edit form 97 | if(!model.containsAttribute("gif")) { 98 | model.addAttribute("gif",gifService.findById(gifId)); 99 | } 100 | model.addAttribute("categories",categoryService.findAll()); 101 | model.addAttribute("action",String.format("/gifs/%s",gifId)); 102 | model.addAttribute("heading","Edit GIF"); 103 | model.addAttribute("submit","Update"); 104 | 105 | return "gif/form"; 106 | } 107 | 108 | // Update an existing GIF 109 | @RequestMapping(value = "/gifs/{gifId}", method = RequestMethod.POST) 110 | public String updateGif(Gif gif, @RequestParam MultipartFile file, RedirectAttributes redirectAttributes) { 111 | // TODO: Update GIF if data is valid 112 | gifService.save(gif,file); 113 | 114 | // Flash message 115 | redirectAttributes.addFlashAttribute("flash",new FlashMessage("GIF successfully updated!", FlashMessage.Status.SUCCESS)); 116 | 117 | // Redirect browser to updated GIF's detail view 118 | return String.format("redirect:/gifs/%s",gif.getId()); 119 | } 120 | 121 | // Delete an existing GIF 122 | @RequestMapping(value = "/gifs/{gifId}/delete", method = RequestMethod.POST) 123 | public String deleteGif(@PathVariable Long gifId, RedirectAttributes redirectAttributes) { 124 | // TODO: Delete the GIF whose id is gifId 125 | Gif gif = gifService.findById(gifId); 126 | gifService.delete(gif); 127 | redirectAttributes.addFlashAttribute("flash",new FlashMessage("GIF deleted!", FlashMessage.Status.SUCCESS)); 128 | 129 | // TODO: Redirect to app root 130 | return "redirect:/"; 131 | } 132 | 133 | // Mark/unmark an existing GIF as a favorite 134 | @RequestMapping(value = "/gifs/{gifId}/favorite", method = RequestMethod.POST) 135 | public String toggleFavorite(@PathVariable Long gifId, HttpServletRequest request) { 136 | // TODO: With GIF whose id is gifId, toggle the favorite field 137 | Gif gif = gifService.findById(gifId); 138 | gifService.toggleFavorite(gif); 139 | 140 | // TODO: Redirect to GIF's detail view 141 | return String.format("redirect:%s",request.getHeader("referer")); 142 | } 143 | 144 | // Search results 145 | @RequestMapping("/search") 146 | public String searchResults(@RequestParam String q, Model model) { 147 | // TODO: Get list of GIFs whose description contains value specified by q 148 | List gifs = new ArrayList<>(); 149 | 150 | model.addAttribute("gifs",gifs); 151 | return "gif/index"; 152 | } 153 | } -------------------------------------------------------------------------------- /src/main/resources/app.properties: -------------------------------------------------------------------------------- 1 | # Hash salt for shortened URLs 2 | giflib.hash.salt = xOBtdmJZxRcz^jkkyHfkrkT1*02bJUn+YQts0*xCeka%cGHCN1fjaC*faFtY 3 | 4 | # Package where our entities (models) are located 5 | giflib.entity.package = com.teamtreehouse.giflib.model 6 | 7 | # Details for our datasource 8 | giflib.db.driver = org.h2.Driver 9 | giflib.db.url = jdbc:h2:mem:giflib 10 | #giflib.db.url = jdbc:h2:tcp://localhost/~/Code/screencasts/giflib-hibernate/data/giflib 11 | giflib.db.username = sa 12 | giflib.db.password = -------------------------------------------------------------------------------- /src/main/resources/hibernate.cfg.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | org.hibernate.dialect.H2Dialect 9 | 10 | 11 | org.hibernate.cache.internal.NoCacheProvider 12 | 13 | 14 | true 15 | 16 | 17 | org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl 18 | 19 | 20 | update 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/resources/static/app.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Varela+Round); 2 | 3 | body { 4 | font-family: 'varela round', sans-serif; 5 | } 6 | 7 | h2 { 8 | font-size: 1.6rem; 9 | margin-bottom: 0; 10 | } 11 | 12 | nav { 13 | background-color: #896bb3; 14 | box-shadow: none; 15 | } 16 | 17 | nav .brand-logo, nav ul li.active a { 18 | color: #fefefe; 19 | } 20 | 21 | nav ul li a { 22 | font-size: 1.1rem; 23 | } 24 | 25 | nav ul li a, nav .brand-logo span { 26 | color: #d6c5ec; 27 | } 28 | 29 | nav ul li.active, nav ul li:hover { 30 | background-color: inherit; 31 | } 32 | 33 | #mobile-nav, #mobile-nav li.active, #mobile-nav li:hover { 34 | background-color: #37333d; 35 | } 36 | 37 | #mobile-nav li a { 38 | font-size: 1.3rem; 39 | color: #906fbc; 40 | } 41 | 42 | #mobile-nav li.active a { 43 | color: #fff; 44 | } 45 | 46 | #mobile-nav li { 47 | border-bottom: 1px solid #27252b; 48 | } 49 | 50 | .search-bar .col { 51 | padding: 0; 52 | } 53 | 54 | .search-bar .input-field { 55 | background-color: #f7f5fa; 56 | } 57 | 58 | .search-bar .input-field { 59 | border: 1px solid #dad7de; 60 | border-radius: 4px; 61 | } 62 | 63 | .search-bar .input-field input { 64 | border: none; 65 | margin: 0; 66 | padding-left: 1rem; 67 | font-size: 1.1rem; 68 | } 69 | 70 | .search-bar .input-field input:focus { 71 | background-color: transparent; 72 | } 73 | 74 | .search-bar .input-field input ~ .material-icons, .search-bar .input-field input:focus ~ .material-icons { 75 | top: 0.5rem; 76 | color: #896bb3; 77 | } 78 | 79 | ::-webkit-input-placeholder { 80 | color: #a29fa6; 81 | } 82 | 83 | .search-bar .input-field input, .search-bar .input-field input:focus { 84 | color: #757378; 85 | } 86 | 87 | .gifs.container .col:nth-child(3n+1) { 88 | padding-left: 0; 89 | } 90 | 91 | .gifs.container .col:nth-child(3n+3) { 92 | padding-right: 0; 93 | } 94 | 95 | .gifs img { 96 | height: 220px; 97 | width: 100%; 98 | border-radius: 6px; 99 | } 100 | 101 | .col { 102 | margin-bottom: 0.75rem; 103 | position: relative; 104 | } 105 | 106 | .container .row { 107 | margin-left: 0; 108 | margin-right: 0; 109 | } 110 | 111 | .favorite { 112 | position: absolute; 113 | top: 0; 114 | right: 0.75rem; 115 | display: block; 116 | height: 60px; 117 | width: 60px; 118 | z-index: 100; 119 | border-radius: 0 6px 0 0; 120 | } 121 | 122 | .col:nth-child(3n) .favorite { 123 | right: 0; 124 | } 125 | 126 | .gif-detail .favorite { 127 | right: 0; 128 | } 129 | 130 | .mark.favorite { 131 | background: url(icons/favorite.png) no-repeat 0 0; 132 | } 133 | 134 | .unmark.favorite { 135 | background: url(icons/unfavorite.png) no-repeat 0 0; 136 | } 137 | 138 | .gif-detail .meta { 139 | margin-bottom: 0.6rem; 140 | } 141 | 142 | .gif-detail .divider { 143 | margin-bottom: 0; 144 | } 145 | 146 | .gif-detail .col { 147 | padding-right: 0; 148 | } 149 | 150 | .gif-detail .frame .col { 151 | padding: 1.5rem 5rem; 152 | text-align: center; 153 | border: 1px solid #e3e1e6; 154 | background-color: #f7f5fa; 155 | border-radius: 6px; 156 | } 157 | 158 | .gif-detail img { 159 | display: inline-block; 160 | width: 100%; 161 | } 162 | 163 | .gif-detail .meta h4 { 164 | color: #896bb3; 165 | font-size: 1.5rem; 166 | margin-bottom: 0.6rem; 167 | margin-top: 0.2rem; 168 | } 169 | 170 | .gif-detail .meta p { 171 | margin-top: 3px 0; 172 | color: #a9a7ac; 173 | } 174 | 175 | #sidenav-overlay { 176 | background-color: transparent; 177 | } 178 | 179 | .share { 180 | border: 1px solid #e3e1e6; 181 | border-radius: 4px; 182 | padding: 0.5rem 0.5rem 0.5rem 1rem; 183 | margin-top: 8px; 184 | } 185 | 186 | .share .btn { 187 | background-color: #896bb3; 188 | box-shadow: none; 189 | text-transform: none; 190 | color: #fefefe; 191 | } 192 | 193 | .share span { 194 | color: #896bb3; 195 | line-height: 36px; 196 | } 197 | 198 | .card { 199 | box-shadow: none; 200 | border-radius: 4px; 201 | margin-top: 0; 202 | margin-bottom: 0.8rem; 203 | } 204 | 205 | .card .card-content { 206 | padding: 0; 207 | } 208 | 209 | .card .card-title { 210 | text-align: center; 211 | font-size: 1.3rem; 212 | } 213 | 214 | .card .card-title a { 215 | color: #fefefe; 216 | display: block; 217 | line-height: 2rem; 218 | }Ã¥ 219 | 220 | .btn-ghost { 221 | color: #59b3b3; 222 | border: 1px solid #59b3b3; 223 | padding: 10px; 224 | height: initial; 225 | text-transform: uppercase; 226 | text-align: center; 227 | -webkit-transition: all 0.3s; 228 | } 229 | 230 | .btn-ghost:hover { 231 | background-color: #59b3b3; 232 | color: #ffffff; 233 | } 234 | 235 | .btn-ghost .material-icons { 236 | position: relative; 237 | display: inline-block; 238 | top: 6px; 239 | } 240 | 241 | .meta .actions { 242 | display: inline-block; 243 | } 244 | 245 | .meta .actions:before { 246 | content: "\00b7"; 247 | margin: 0 10px; 248 | color: #bebcc0; 249 | } 250 | 251 | .actions form { 252 | display: inline; 253 | } 254 | 255 | .actions button { 256 | background: none; 257 | border: none; 258 | padding: 0; 259 | } 260 | 261 | .actions a, .actions button { 262 | color: #b8a6d1; 263 | } 264 | 265 | .delete .button[type=submit] { 266 | background:#f2f0f5; 267 | color:#a29fa6; 268 | float:right; 269 | position:relative; 270 | top: -71px; 271 | margin-right: 0; 272 | } 273 | 274 | .flash { 275 | margin-top: 2rem; 276 | margin-bottom: 2rem; 277 | padding: 1rem 2rem; 278 | border-radius: 4px; 279 | font-size: 1.1rem; 280 | } 281 | 282 | .flash.success { 283 | background-color: #7f9e40; 284 | color: #f3f6ee; 285 | } 286 | 287 | .flash.failure { 288 | background-color: #ac4343; 289 | color: #f2e2e2; 290 | } 291 | 292 | i[data-close] { 293 | margin: 1rem; 294 | color: #fff; 295 | cursor: default; 296 | cursor: pointer; 297 | } 298 | 299 | .meta span { 300 | color: #bebcc0; 301 | } 302 | 303 | .meta .category a { 304 | color: #cd9c92; 305 | } 306 | 307 | .meta .category:before { 308 | content: "Category: "; 309 | } 310 | 311 | .meta span:first-child:after, .meta .actions a:after { 312 | content: "\00b7"; 313 | margin: 0 10px; 314 | } 315 | 316 | .categories { 317 | margin-top: 2rem; 318 | } 319 | 320 | .categories .row:first-child, .categories .row:first-child h2 { 321 | margin: 0; 322 | } 323 | 324 | h2 { 325 | color: #886ab2; 326 | } 327 | 328 | .gifs.container h2 { 329 | padding-bottom: 10px; 330 | border-bottom: 1px solid #e0e0e0; 331 | margin-bottom: 20px; 332 | } 333 | 334 | .categories .row:first-child * { 335 | color: #886ab2; 336 | } 337 | 338 | .categories .row:first-child > .col > a { 339 | font-size: 3rem; 340 | line-height: 3rem; 341 | color: #886ab2; 342 | font-family: roboto; 343 | font-weight: 200; 344 | margin: -6px; 345 | } 346 | 347 | .categories .row:first-child .col { 348 | padding-right: 1.3rem; 349 | } 350 | 351 | .categories .card-title { 352 | text-align: center; 353 | padding: 3rem 0 2rem 0; 354 | } 355 | 356 | .categories .card-title a.edit { 357 | height: 1.6rem; 358 | display: inline-block; 359 | background-color: rgba(255,255,255,0.2); 360 | border-radius: 4px; 361 | padding: 0 8px; 362 | } 363 | 364 | .categories .card-title a.name:after { 365 | content: "\A"; 366 | } 367 | 368 | .divider { 369 | margin: 0 0 1.6rem 0; 370 | } 371 | 372 | div.cs-skin-border { 373 | background: #f7f5fa; 374 | max-width:initial; 375 | } 376 | 377 | .cs-skin-border .cs-options { 378 | font-size: 1.2rem; 379 | } 380 | 381 | .cs-skin-border { 382 | font-family: inherit !important; 383 | color: #a29fa6; 384 | } 385 | 386 | .cs-skin-border > span { 387 | border: 1px solid #dad7de; 388 | border-radius: 6px; 389 | font-size: 1.2rem; 390 | font-weight: 200; 391 | padding: 1rem; 392 | } 393 | 394 | .cs-skin-border.cs-active > span { 395 | color: #a29fa6; 396 | } 397 | 398 | .cs-skin-border > span:after { 399 | font-family: 'Material Icons'; 400 | font-weight: normal; 401 | font-style: normal; 402 | font-size: 24px; /* Preferred icon size */ 403 | display: inline-block; 404 | width: 1em; 405 | height: 1em; 406 | line-height: 1; 407 | text-transform: none; 408 | letter-spacing: normal; 409 | word-wrap: normal; 410 | white-space: nowrap; 411 | direction: ltr; 412 | 413 | /* Support for all WebKit browsers. */ 414 | -webkit-font-smoothing: antialiased; 415 | /* Support for Safari and Chrome. */ 416 | text-rendering: optimizeLegibility; 417 | 418 | /* Support for Firefox. */ 419 | -moz-osx-font-smoothing: grayscale; 420 | 421 | /* Support for IE. */ 422 | font-feature-settings: 'liga'; 423 | 424 | content: "keyboard_arrow_down"; 425 | } 426 | 427 | .cs-skin-border ul span { 428 | padding: 1rem; 429 | } 430 | 431 | .cs-skin-border.cs-active > span { 432 | background: #f7f5fa; 433 | border: 1px solid #dad7de; 434 | border-bottom: none; 435 | border-bottom-right-radius: 0; 436 | border-bottom-left-radius: 0; 437 | } 438 | 439 | .cs-skin-border.cs-active .cs-options { 440 | background: #f7f5fa; 441 | border: 1px solid #dad7de; 442 | border-top: none; 443 | border-radius: 0 0 6px 6px; 444 | } 445 | 446 | div.cs-select { 447 | max-width:initial; 448 | } 449 | 450 | form .row .col { 451 | margin-bottom: 0; 452 | } 453 | 454 | input[type=text] { 455 | display: block; 456 | border: 1px solid #dad7de; 457 | border-radius: 6px; 458 | background: #f7f5fa; 459 | padding: 1rem; 460 | font-size: 1.2rem; 461 | line-height: 1.5; 462 | height: initial; 463 | box-sizing: border-box; 464 | margin-bottom: 0; 465 | color: #7a7580; 466 | } 467 | 468 | input[type=text]:focus:not([readonly]) { 469 | border-bottom-color: #dad7de; 470 | box-shadow: none; 471 | } 472 | 473 | .button { 474 | margin-right: 1rem; 475 | display: inline-block; 476 | background: #f2f0f5; 477 | border-radius: 4px; 478 | border: none; 479 | color: #a29fa6; 480 | line-height: 1.5; 481 | font-size: 0.9rem; 482 | padding: 1rem 2rem; 483 | text-align: center; 484 | } 485 | 486 | .button[type=submit] { 487 | background: #896bb3; 488 | color: #fefefe; 489 | } 490 | 491 | .user-meta { 492 | text-align:center; 493 | color: #a9a7ac; 494 | margin-bottom: 0.4rem; 495 | } 496 | 497 | .user-meta h2 { 498 | margin-top: 0; 499 | margin-bottom: 0.2rem; 500 | } 501 | 502 | .user-meta img.circle { 503 | max-width: 5rem; 504 | } 505 | 506 | .file-wrapper { 507 | border: 1px solid #dad7de; 508 | border-radius: 6px; 509 | background: #f7f5fa; 510 | } 511 | 512 | .file-wrapper:after { 513 | content: ""; 514 | display: table; 515 | clear: both; 516 | } 517 | 518 | input[type=file] { 519 | width: 0.1px; 520 | height: 0.1px; 521 | opacity: 0; 522 | overflow: hidden; 523 | position: absolute; 524 | z-index: -1; 525 | } 526 | 527 | label.button { 528 | background: #896bb3; 529 | color: #fefefe; 530 | padding: 1rem 1.4rem; 531 | float: right; 532 | margin: 0.2rem; 533 | } 534 | 535 | .file-wrapper .placeholder { 536 | display: inline-block; 537 | padding: 1rem; 538 | color: #886ab2; 539 | font-size: 1.2rem; 540 | } 541 | 542 | .subtitle { 543 | color: #a9a7ac; 544 | margin-top: 1rem; 545 | } 546 | 547 | /* Form validation messages */ 548 | .error input { 549 | border-width: 2px; 550 | border-color: #ac4343; 551 | border-bottom: none; 552 | border-bottom-left-radius: 0; 553 | border-bottom-right-radius: 0; 554 | } 555 | 556 | .error .error-message { 557 | border: 2px solid #ac4343; 558 | border-radius: 0 0 6px 6px; 559 | background-color: #ac4343; 560 | color: rgb(254,251,251); 561 | padding: 10px; 562 | } 563 | 564 | .file-wrapper.error { 565 | border-width: 2px; 566 | border-color: #ac4343; 567 | } 568 | 569 | .file-wrapper.error .error-message { 570 | border-radius: 0; 571 | } 572 | 573 | .error:not(.file-wrapper) { 574 | display: flex; 575 | display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */ 576 | display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */ 577 | display: -ms-flexbox; /* TWEENER - IE 10 */ 578 | display: -webkit-flex; 579 | flex-direction: column; 580 | -webkit-flex-direction: column; 581 | } 582 | 583 | .error .cs-select { 584 | order: -1; 585 | -webkit-order: -1; 586 | } 587 | 588 | .error .cs-select.cs-skin-border:not(.cs-active) .cs-placeholder { 589 | border-bottom-left-radius: 0; 590 | border-bottom-right-radius: 0; 591 | border-color: #ac4343; 592 | border-width: 2px; 593 | } 594 | 595 | 596 | @media only screen and (max-width: 993px) { 597 | nav .brand-logo { 598 | left: 0; 599 | -webkit-transform: none; 600 | transform: none; 601 | position: static; 602 | } 603 | 604 | .container { 605 | width: 100%; 606 | } 607 | 608 | .gifs.container { 609 | padding: 0; 610 | margin-top: 1.1rem; 611 | } 612 | 613 | .gifs.container .col { 614 | padding: 0 0.75rem !important; 615 | } 616 | 617 | .col { 618 | padding-right: 0; 619 | } 620 | 621 | .navbar-fixed .container { 622 | padding-left: 1.5rem; 623 | padding-right: 1.5rem; 624 | } 625 | 626 | .search-bar { 627 | margin-bottom: 1rem; 628 | } 629 | 630 | .search-bar div { 631 | margin-bottom:0; 632 | } 633 | 634 | .search-bar .input-field { 635 | margin-top: 0; 636 | border: none; 637 | border-radius: 0; 638 | } 639 | 640 | .search-bar .input-field input { 641 | height: 4rem; 642 | padding-left: 1.5rem; 643 | } 644 | 645 | .search-bar .input-field input ~ .material-icons, .search-bar .input-field input:focus ~ .material-icons { 646 | top: 1rem; 647 | right: 1rem; 648 | } 649 | 650 | .gifs { 651 | padding: 0 1.5rem; 652 | } 653 | 654 | .gifs img { 655 | width: 100%; 656 | } 657 | 658 | .gif-detail .meta { 659 | margin-bottom: 1.2rem; 660 | } 661 | 662 | .gif-detail .divider { 663 | margin-bottom: 0.6rem; 664 | } 665 | 666 | .gif-detail .frame .col { 667 | padding: 0 1rem; 668 | background: none; 669 | border: none; 670 | } 671 | 672 | .gif-detail .col { 673 | margin-bottom: 0.1rem; 674 | } 675 | 676 | .gif-detail .col { 677 | padding-right: 1rem; 678 | } 679 | 680 | .gif-detail img { 681 | border-radius: 6px; 682 | } 683 | .gif-detail .favorite { 684 | right: 1rem; 685 | } 686 | 687 | .gif-detail .meta .circle { 688 | border-radius: 50%; 689 | } 690 | 691 | .chip { 692 | margin-left: 0.8rem; 693 | } 694 | 695 | .meta { 696 | padding-right: 10px; 697 | padding-left: 10px; 698 | } 699 | 700 | .meta .category:before { 701 | content: none; 702 | } 703 | 704 | .meta .actions { 705 | display: block; 706 | margin-top: 0.4rem; 707 | } 708 | 709 | .meta .actions:before { 710 | content: none; 711 | margin: 0; 712 | } 713 | 714 | .categories .card { 715 | margin: 0; 716 | } 717 | 718 | .categories .card-title { 719 | text-align: left; 720 | padding: 1rem; 721 | } 722 | 723 | .categories .card-title a.edit { 724 | background: none; 725 | } 726 | 727 | .categories .card-title a.name:after { 728 | content: none; 729 | } 730 | 731 | .categories .card-title a.name { 732 | float: left; 733 | } 734 | 735 | .categories .card-title a.edit { 736 | float: right; 737 | color: rgba(255,255,255,0.5); 738 | } 739 | 740 | .categories .card-title a.edit i { 741 | line-height: 2rem; 742 | } 743 | 744 | .categories .card-title:after { 745 | content: ""; 746 | display: table; 747 | clear: both; 748 | } 749 | 750 | .flash { 751 | margin: 0; 752 | border-radius: 0; 753 | padding: 1rem; 754 | } 755 | 756 | .user-meta { 757 | text-align:left; 758 | margin-top: 1rem; 759 | } 760 | 761 | .user-meta h2 { 762 | margin-top: 1rem; 763 | } 764 | 765 | .divider { 766 | margin: 0 0.75rem 1.6rem 0.75rem; 767 | } 768 | 769 | .user-meta img.circle { 770 | max-width: 100%; 771 | } 772 | 773 | .user-meta .meta { 774 | padding: 0; 775 | } 776 | 777 | .share { 778 | margin-left: 1rem; 779 | margin-right: 1rem; 780 | } 781 | 782 | .delete .button[type=submit] { 783 | top: -67px; 784 | } 785 | 786 | .gifs.container h2 { 787 | margin-left: 12px; 788 | margin-right: 12px; 789 | } 790 | 791 | .col:nth-child(3n) .favorite { 792 | right: 0.75rem; 793 | } 794 | } 795 | 796 | /* Bugs in Materialize */ 797 | @media only screen and (min-width: 993px) { 798 | 799 | .row .col.offset-l2 { 800 | margin-left: 16.66667%; 801 | } 802 | 803 | .row .col.offset-l1 { 804 | margin-left: 8.33333%; 805 | } 806 | } -------------------------------------------------------------------------------- /src/main/resources/static/app.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | // Activate mobile nav toggle button 3 | $(".button-collapse").sideNav({edge: 'right'}); 4 | 5 | // Activate close icons for dismissible alerts 6 | $('[data-close]').on('click',function(){ 7 | $(this).parent().remove(); 8 | }); 9 | 10 | // Capture change event on file inputs 11 | $('input[type=file]').on("change",function(){ 12 | var value = $(this).val().split('\\').pop(); 13 | value = value == null || value == '' ? $(this).find('.placeholder').data('placeholder') : value; 14 | $(this).parent('.file-wrapper').find('.placeholder').text(value); 15 | }); 16 | 17 | var onChange = function(obj) { 18 | // Get option style 19 | var style = $(obj.selEl).find('li.cs-selected').attr('style'); 20 | 21 | // Set style of parent cs-select placeholder to style of option 22 | $(obj.selPlaceholder).attr('style',style); 23 | }; 24 | 25 | var onLoad = function(obj) { 26 | // Get selected option 27 | var $option = $(obj.el).find('option[selected]'); 28 | 29 | if($option.length > 0) { 30 | // Get option style 31 | var style = $(obj.el).find('option[selected]').attr('style'); 32 | 33 | // Set style of parent cs-select placeholder to style of option 34 | $(obj.selPlaceholder).attr('style',style); 35 | 36 | // Mark selected item as selected 37 | $(obj.selEl).find('ul li[data-value="' + obj.el.value + '"]').addClass('cs-selected'); 38 | } else { 39 | // Unmark list items as cs-selected, since first is selected by default 40 | $(obj.selEl).find('ul li').removeClass('cs-selected'); 41 | 42 | // Set select element's value to empty string 43 | $(obj.el).val(''); 44 | } 45 | }; 46 | 47 | // Activate Codrops select magic 48 | (function() { 49 | [].slice.call( document.querySelectorAll( 'select' ) ).forEach( function(el) { 50 | new SelectFx(el,{onLoad:onLoad,onChange:onChange}); 51 | }); 52 | })(); 53 | }); -------------------------------------------------------------------------------- /src/main/resources/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/favicon.png -------------------------------------------------------------------------------- /src/main/resources/static/icons/favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/icons/favorite.png -------------------------------------------------------------------------------- /src/main/resources/static/icons/unfavorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/icons/unfavorite.png -------------------------------------------------------------------------------- /src/main/resources/static/images/me.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/images/me.jpg -------------------------------------------------------------------------------- /src/main/resources/static/vendor/codrops/css/cs-select.css: -------------------------------------------------------------------------------- 1 | /* Default custom select styles */ 2 | div.cs-select { 3 | display: inline-block; 4 | vertical-align: middle; 5 | position: relative; 6 | text-align: left; 7 | background: #fff; 8 | z-index: 100; 9 | width: 100%; 10 | max-width: 500px; 11 | -webkit-touch-callout: none; 12 | -webkit-user-select: none; 13 | -khtml-user-select: none; 14 | -moz-user-select: none; 15 | -ms-user-select: none; 16 | user-select: none; 17 | } 18 | 19 | div.cs-select:focus { 20 | outline: none; /* For better accessibility add a style for this in your skin */ 21 | } 22 | 23 | .cs-select select { 24 | display: none; 25 | } 26 | 27 | .cs-select span { 28 | display: block; 29 | position: relative; 30 | cursor: pointer; 31 | padding: 1em; 32 | white-space: nowrap; 33 | overflow: hidden; 34 | text-overflow: ellipsis; 35 | } 36 | 37 | /* Placeholder and selected option */ 38 | .cs-select > span { 39 | padding-right: 3em; 40 | } 41 | 42 | .cs-select > span::after, 43 | .cs-select .cs-selected span::after { 44 | speak: none; 45 | position: absolute; 46 | top: 50%; 47 | -webkit-transform: translateY(-50%); 48 | transform: translateY(-50%); 49 | -webkit-font-smoothing: antialiased; 50 | -moz-osx-font-smoothing: grayscale; 51 | } 52 | 53 | .cs-select > span::after { 54 | content: '\25BE'; 55 | right: 1em; 56 | } 57 | 58 | .cs-select .cs-selected span::after { 59 | content: '\2713'; 60 | margin-left: 1em; 61 | } 62 | 63 | .cs-select.cs-active > span::after { 64 | -webkit-transform: translateY(-50%) rotate(180deg); 65 | transform: translateY(-50%) rotate(180deg); 66 | } 67 | 68 | div.cs-active { 69 | z-index: 200; 70 | } 71 | 72 | /* Options */ 73 | .cs-select .cs-options { 74 | position: absolute; 75 | overflow: hidden; 76 | width: 100%; 77 | background: #fff; 78 | visibility: hidden; 79 | } 80 | 81 | .cs-select.cs-active .cs-options { 82 | visibility: visible; 83 | } 84 | 85 | .cs-select ul { 86 | list-style: none; 87 | margin: 0; 88 | padding: 0; 89 | width: 100%; 90 | } 91 | 92 | .cs-select ul span { 93 | padding: 1em; 94 | } 95 | 96 | .cs-select ul li.cs-focus span { 97 | background-color: #ddd; 98 | } 99 | 100 | /* Optgroup and optgroup label */ 101 | .cs-select li.cs-optgroup ul { 102 | padding-left: 1em; 103 | } 104 | 105 | .cs-select li.cs-optgroup > span { 106 | cursor: default; 107 | } 108 | -------------------------------------------------------------------------------- /src/main/resources/static/vendor/codrops/css/cs-skin-border.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src:url('../fonts/icomoon/icomoon.eot?-rdnm34'); 4 | src:url('../fonts/icomoon/icomoon.eot?#iefix-rdnm34') format('embedded-opentype'), 5 | url('../fonts/icomoon/icomoon.woff?-rdnm34') format('woff'), 6 | url('../fonts/icomoon/icomoon.ttf?-rdnm34') format('truetype'), 7 | url('../fonts/icomoon/icomoon.svg?-rdnm34#icomoon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | div.cs-skin-border { 13 | background: transparent; 14 | font-size: 2em; 15 | font-weight: 700; 16 | max-width: 600px; 17 | } 18 | 19 | @media screen and (max-width: 30em) { 20 | .cs-skin-border { font-size: 1em; } 21 | } 22 | 23 | .cs-skin-border > span { 24 | border: 5px solid #000; 25 | border-color: inherit; 26 | transition: background 0.2s, border-color 0.2s; 27 | } 28 | 29 | .cs-skin-border > span::after, 30 | .cs-skin-border .cs-selected span::after { 31 | font-family: 'icomoon'; 32 | content: '\e000'; 33 | } 34 | 35 | .cs-skin-border ul span::after { 36 | content: ''; 37 | opacity: 0; 38 | } 39 | 40 | .cs-skin-border .cs-selected span::after { 41 | content: '\e00e'; 42 | color: #ddd9c9; 43 | font-size: 1.5em; 44 | opacity: 1; 45 | transition: opacity 0.2s; 46 | } 47 | 48 | .cs-skin-border.cs-active > span { 49 | background: #fff; 50 | border-color: #fff; 51 | color: #2980b9; 52 | } 53 | 54 | .cs-skin-border .cs-options { 55 | color: #2980b9; 56 | font-size: 0.75em; 57 | opacity: 0; 58 | transition: opacity 0.2s, visibility 0s 0.2s; 59 | } 60 | 61 | .cs-skin-border.cs-active .cs-options { 62 | opacity: 1; 63 | transition: opacity 0.2s; 64 | } 65 | 66 | .cs-skin-border ul span { 67 | padding: 1em 2em; 68 | backface-visibility: hidden; 69 | } 70 | 71 | .cs-skin-border .cs-options li span:hover, 72 | .cs-skin-border li.cs-focus span { 73 | background: #f5f3ec; 74 | } -------------------------------------------------------------------------------- /src/main/resources/static/vendor/codrops/fonts/codropsicons/codropsicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/codrops/fonts/codropsicons/codropsicons.eot -------------------------------------------------------------------------------- /src/main/resources/static/vendor/codrops/fonts/codropsicons/codropsicons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a custom SVG font generated by IcoMoon. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/resources/static/vendor/codrops/fonts/codropsicons/codropsicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/codrops/fonts/codropsicons/codropsicons.ttf -------------------------------------------------------------------------------- /src/main/resources/static/vendor/codrops/fonts/codropsicons/codropsicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/codrops/fonts/codropsicons/codropsicons.woff -------------------------------------------------------------------------------- /src/main/resources/static/vendor/codrops/fonts/codropsicons/license.txt: -------------------------------------------------------------------------------- 1 | Icon Set: Font Awesome -- http://fortawesome.github.com/Font-Awesome/ 2 | License: SIL -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL 3 | 4 | 5 | Icon Set: Eco Ico -- http://dribbble.com/shots/665585-Eco-Ico 6 | License: CC0 -- http://creativecommons.org/publicdomain/zero/1.0/ -------------------------------------------------------------------------------- /src/main/resources/static/vendor/codrops/fonts/icomoon/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/codrops/fonts/icomoon/icomoon.eot -------------------------------------------------------------------------------- /src/main/resources/static/vendor/codrops/fonts/icomoon/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by Fontastic.me 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/resources/static/vendor/codrops/fonts/icomoon/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/codrops/fonts/icomoon/icomoon.ttf -------------------------------------------------------------------------------- /src/main/resources/static/vendor/codrops/fonts/icomoon/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/codrops/fonts/icomoon/icomoon.woff -------------------------------------------------------------------------------- /src/main/resources/static/vendor/codrops/js/classie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * classie - class helper functions 3 | * from bonzo https://github.com/ded/bonzo 4 | * 5 | * classie.has( elem, 'my-class' ) -> true/false 6 | * classie.add( elem, 'my-new-class' ) 7 | * classie.remove( elem, 'my-unwanted-class' ) 8 | * classie.toggle( elem, 'my-class' ) 9 | */ 10 | 11 | /*jshint browser: true, strict: true, undef: true */ 12 | /*global define: false */ 13 | 14 | ( function( window ) { 15 | 16 | 'use strict'; 17 | 18 | // class helper functions from bonzo https://github.com/ded/bonzo 19 | 20 | function classReg( className ) { 21 | return new RegExp("(^|\\s+)" + className + "(\\s+|$)"); 22 | } 23 | 24 | // classList support for class management 25 | // altho to be fair, the api sucks because it won't accept multiple classes at once 26 | var hasClass, addClass, removeClass; 27 | 28 | if ( 'classList' in document.documentElement ) { 29 | hasClass = function( elem, c ) { 30 | return elem.classList.contains( c ); 31 | }; 32 | addClass = function( elem, c ) { 33 | elem.classList.add( c ); 34 | }; 35 | removeClass = function( elem, c ) { 36 | elem.classList.remove( c ); 37 | }; 38 | } 39 | else { 40 | hasClass = function( elem, c ) { 41 | return classReg( c ).test( elem.className ); 42 | }; 43 | addClass = function( elem, c ) { 44 | if ( !hasClass( elem, c ) ) { 45 | elem.className = elem.className + ' ' + c; 46 | } 47 | }; 48 | removeClass = function( elem, c ) { 49 | elem.className = elem.className.replace( classReg( c ), ' ' ); 50 | }; 51 | } 52 | 53 | function toggleClass( elem, c ) { 54 | var fn = hasClass( elem, c ) ? removeClass : addClass; 55 | fn( elem, c ); 56 | } 57 | 58 | var classie = { 59 | // full names 60 | hasClass: hasClass, 61 | addClass: addClass, 62 | removeClass: removeClass, 63 | toggleClass: toggleClass, 64 | // short names 65 | has: hasClass, 66 | add: addClass, 67 | remove: removeClass, 68 | toggle: toggleClass 69 | }; 70 | 71 | // transport 72 | if ( typeof define === 'function' && define.amd ) { 73 | // AMD 74 | define( classie ); 75 | } else { 76 | // browser global 77 | window.classie = classie; 78 | } 79 | 80 | })( window ); 81 | -------------------------------------------------------------------------------- /src/main/resources/static/vendor/codrops/js/selectFx.js: -------------------------------------------------------------------------------- 1 | /** 2 | * selectFx.js v1.0.0 3 | * http://www.codrops.com 4 | * 5 | * Licensed under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 8 | * Copyright 2014, Codrops 9 | * http://www.codrops.com 10 | */ 11 | ;( function( window ) { 12 | 13 | 'use strict'; 14 | 15 | /** 16 | * based on from https://github.com/inuyaksa/jquery.nicescroll/blob/master/jquery.nicescroll.js 17 | */ 18 | function hasParent( e, p ) { 19 | if (!e) return false; 20 | var el = e.target||e.srcElement||e||false; 21 | while (el && el != p) { 22 | el = el.parentNode||false; 23 | } 24 | return (el!==false); 25 | }; 26 | 27 | /** 28 | * extend obj function 29 | */ 30 | function extend( a, b ) { 31 | for( var key in b ) { 32 | if( b.hasOwnProperty( key ) ) { 33 | a[key] = b[key]; 34 | } 35 | } 36 | return a; 37 | } 38 | 39 | /** 40 | * SelectFx function 41 | */ 42 | function SelectFx( el, options ) { 43 | this.el = el; 44 | this.options = extend( {}, this.options ); 45 | extend( this.options, options ); 46 | this._init(); 47 | } 48 | 49 | /** 50 | * SelectFx options 51 | */ 52 | SelectFx.prototype.options = { 53 | // if true all the links will open in a new tab. 54 | // if we want to be redirected when we click an option, we need to define a data-link attr on the option of the native select element 55 | newTab : true, 56 | // when opening the select element, the default placeholder (if any) is shown 57 | stickyPlaceholder : true, 58 | // callback when changing the value 59 | onChange : function( obj ) { return false; }, 60 | // callback for load 61 | onLoad : function ( obj ) { return false; } 62 | } 63 | 64 | /** 65 | * init function 66 | * initialize and cache some vars 67 | */ 68 | SelectFx.prototype._init = function() { 69 | // check if we are using a placeholder for the native select box 70 | // we assume the placeholder is disabled and selected by default 71 | var selectedOpt = this.el.querySelector( 'option[selected]' ); 72 | this.hasDefaultPlaceholder = selectedOpt && selectedOpt.disabled; 73 | 74 | // get selected option (either the first option with attr selected or just the first option) 75 | this.selectedOpt = selectedOpt || this.el.querySelector( 'option' ); 76 | 77 | // create structure 78 | this._createSelectEl(); 79 | 80 | // all options 81 | this.selOpts = [].slice.call( this.selEl.querySelectorAll( 'li[data-option]' ) ); 82 | 83 | // total options 84 | this.selOptsCount = this.selOpts.length; 85 | 86 | // current index 87 | this.current = this.selOpts.indexOf( this.selEl.querySelector( 'li.cs-selected' ) ) || -1; 88 | 89 | // placeholder elem 90 | this.selPlaceholder = this.selEl.querySelector( 'span.cs-placeholder' ); 91 | 92 | // init events 93 | this._initEvents(); 94 | 95 | // callback 96 | this.options.onLoad( this ); 97 | } 98 | 99 | /** 100 | * creates the structure for the select element 101 | */ 102 | SelectFx.prototype._createSelectEl = function() { 103 | var self = this, options = '', createOptionHTML = function(el) { 104 | var optclass = '', classes = '', link = '', style = ''; 105 | 106 | if( el.selectedOpt && !this.foundSelected && !this.hasDefaultPlaceholder ) { 107 | classes += 'cs-selected '; 108 | this.foundSelected = true; 109 | } 110 | // extra classes 111 | if( el.getAttribute( 'data-class' ) ) { 112 | classes += el.getAttribute( 'data-class' ); 113 | } 114 | // link options 115 | if( el.getAttribute( 'data-link' ) ) { 116 | link = 'data-link=' + el.getAttribute( 'data-link' ); 117 | } 118 | 119 | if( classes !== '' ) { 120 | optclass = 'class="' + classes + '" '; 121 | } 122 | 123 | // Include custom styles 124 | if(el.getAttribute( 'style' )) { 125 | style = 'style="' + el.getAttribute( 'style' ) + '" '; 126 | } 127 | 128 | return '
  • ' + el.textContent + '
  • '; 129 | }; 130 | 131 | [].slice.call( this.el.children ).forEach( function(el) { 132 | if( el.disabled ) { return; } 133 | 134 | var tag = el.tagName.toLowerCase(); 135 | 136 | if( tag === 'option' ) { 137 | options += createOptionHTML(el); 138 | } 139 | else if( tag === 'optgroup' ) { 140 | options += '
  • ' + el.label + '
      '; 141 | [].slice.call( el.children ).forEach( function(opt) { 142 | options += createOptionHTML(opt); 143 | } ); 144 | options += '
  • '; 145 | } 146 | } ); 147 | 148 | var opts_el = '
      ' + options + '
    '; 149 | this.selEl = document.createElement( 'div' ); 150 | this.selEl.className = this.el.className; 151 | this.selEl.tabIndex = this.el.tabIndex; 152 | this.selEl.innerHTML = '' + this.selectedOpt.textContent + '' + opts_el; 153 | this.el.parentNode.appendChild( this.selEl ); 154 | this.selEl.appendChild( this.el ); 155 | } 156 | 157 | /** 158 | * initialize the events 159 | */ 160 | SelectFx.prototype._initEvents = function() { 161 | var self = this; 162 | 163 | // open/close select 164 | this.selPlaceholder.addEventListener( 'click', function() { 165 | self._toggleSelect(); 166 | } ); 167 | 168 | // clicking the options 169 | this.selOpts.forEach( function(opt, idx) { 170 | opt.addEventListener( 'click', function() { 171 | self.current = idx; 172 | self._changeOption(); 173 | // close select elem 174 | self._toggleSelect(); 175 | } ); 176 | } ); 177 | 178 | // close the select element if the target it´s not the select element or one of its descendants.. 179 | document.addEventListener( 'click', function(ev) { 180 | var target = ev.target; 181 | if( self._isOpen() && target !== self.selEl && !hasParent( target, self.selEl ) ) { 182 | self._toggleSelect(); 183 | } 184 | } ); 185 | 186 | // keyboard navigation events 187 | this.selEl.addEventListener( 'keydown', function( ev ) { 188 | var keyCode = ev.keyCode || ev.which; 189 | 190 | switch (keyCode) { 191 | // up key 192 | case 38: 193 | ev.preventDefault(); 194 | self._navigateOpts('prev'); 195 | break; 196 | // down key 197 | case 40: 198 | ev.preventDefault(); 199 | self._navigateOpts('next'); 200 | break; 201 | // space key 202 | case 32: 203 | ev.preventDefault(); 204 | if( self._isOpen() && typeof self.preSelCurrent != 'undefined' && self.preSelCurrent !== -1 ) { 205 | self._changeOption(); 206 | } 207 | self._toggleSelect(); 208 | break; 209 | // enter key 210 | case 13: 211 | ev.preventDefault(); 212 | if( self._isOpen() && typeof self.preSelCurrent != 'undefined' && self.preSelCurrent !== -1 ) { 213 | self._changeOption(); 214 | self._toggleSelect(); 215 | } 216 | break; 217 | // esc key 218 | case 27: 219 | ev.preventDefault(); 220 | if( self._isOpen() ) { 221 | self._toggleSelect(); 222 | } 223 | break; 224 | } 225 | } ); 226 | } 227 | 228 | /** 229 | * navigate with up/dpwn keys 230 | */ 231 | SelectFx.prototype._navigateOpts = function(dir) { 232 | if( !this._isOpen() ) { 233 | this._toggleSelect(); 234 | } 235 | 236 | var tmpcurrent = typeof this.preSelCurrent != 'undefined' && this.preSelCurrent !== -1 ? this.preSelCurrent : this.current; 237 | 238 | if( dir === 'prev' && tmpcurrent > 0 || dir === 'next' && tmpcurrent < this.selOptsCount - 1 ) { 239 | // save pre selected current - if we click on option, or press enter, or press space this is going to be the index of the current option 240 | this.preSelCurrent = dir === 'next' ? tmpcurrent + 1 : tmpcurrent - 1; 241 | // remove focus class if any.. 242 | this._removeFocus(); 243 | // add class focus - track which option we are navigating 244 | classie.add( this.selOpts[this.preSelCurrent], 'cs-focus' ); 245 | } 246 | } 247 | 248 | /** 249 | * open/close select 250 | * when opened show the default placeholder if any 251 | */ 252 | SelectFx.prototype._toggleSelect = function() { 253 | // remove focus class if any.. 254 | this._removeFocus(); 255 | 256 | if( this._isOpen() ) { 257 | if( this.current !== -1 ) { 258 | // update placeholder text 259 | this.selPlaceholder.textContent = this.selOpts[ this.current ].textContent; 260 | } 261 | classie.remove( this.selEl, 'cs-active' ); 262 | } 263 | else { 264 | if( this.hasDefaultPlaceholder && this.options.stickyPlaceholder ) { 265 | // everytime we open we wanna see the default placeholder text 266 | this.selPlaceholder.textContent = this.selectedOpt.textContent; 267 | } 268 | classie.add( this.selEl, 'cs-active' ); 269 | } 270 | } 271 | 272 | /** 273 | * change option - the new value is set 274 | */ 275 | SelectFx.prototype._changeOption = function() { 276 | // if pre selected current (if we navigate with the keyboard)... 277 | if( typeof this.preSelCurrent != 'undefined' && this.preSelCurrent !== -1 ) { 278 | this.current = this.preSelCurrent; 279 | this.preSelCurrent = -1; 280 | } 281 | 282 | // current option 283 | var opt = this.selOpts[ this.current ]; 284 | 285 | // update current selected value 286 | this.selPlaceholder.textContent = opt.textContent; 287 | 288 | // change native select element´s value 289 | this.el.value = opt.getAttribute( 'data-value' ); 290 | 291 | // remove class cs-selected from old selected option and add it to current selected option 292 | var oldOpt = this.selEl.querySelector( 'li.cs-selected' ); 293 | if( oldOpt ) { 294 | classie.remove( oldOpt, 'cs-selected' ); 295 | } 296 | classie.add( opt, 'cs-selected' ); 297 | 298 | // if there´s a link defined 299 | if( opt.getAttribute( 'data-link' ) ) { 300 | // open in new tab? 301 | if( this.options.newTab ) { 302 | window.open( opt.getAttribute( 'data-link' ), '_blank' ); 303 | } 304 | else { 305 | window.location = opt.getAttribute( 'data-link' ); 306 | } 307 | } 308 | 309 | // callback 310 | this.options.onChange( this ); 311 | } 312 | 313 | /** 314 | * returns true if select element is opened 315 | */ 316 | SelectFx.prototype._isOpen = function(opt) { 317 | return classie.has( this.selEl, 'cs-active' ); 318 | } 319 | 320 | /** 321 | * removes the focus class from the option 322 | */ 323 | SelectFx.prototype._removeFocus = function(opt) { 324 | var focusEl = this.selEl.querySelector( 'li.cs-focus' ) 325 | if( focusEl ) { 326 | classie.remove( focusEl, 'cs-focus' ); 327 | } 328 | } 329 | 330 | /** 331 | * add to global namespace 332 | */ 333 | window.SelectFx = SelectFx; 334 | 335 | } )( window ); 336 | -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 Materialize 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/README.md: -------------------------------------------------------------------------------- 1 | ![alt tag](https://raw.github.com/dogfalo/materialize/master/images/materialize.gif) 2 | =========== 3 | 4 | [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/Dogfalo/materialize?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | Materialize, a CSS Framework based on material design 6 | 7 | ### Current Version : v0.97.0 8 | 9 | ## Sass Requirements: 10 | - Ruby Sass 3.3+, LibSass 0.6+ 11 | 12 | ## Supported Browsers: 13 | Chrome 35+, Firefox 31+, Safari 7+, IE 10+ 14 | 15 | ## Changelog 16 | - v0.97.0 (June 21, 2015) 17 | - **Documentation changed to use Official Google Icon web font** 18 | - **Input errors added** 19 | - Flicker on Firefox on dropdowns fixed 20 | - Pagination made more responsive 21 | - Modal now prevents scrolling 22 | - Modal animation added 23 | - Support for multiple modals added 24 | - Programmatic control of FAB to open/close added 25 | - Programmatic control of slider to play/pause added 26 | - Plus many more bug fixes 27 | - v0.96.1 (April 6, 2015) 28 | - Dropdown Fixes 29 | - Materialize functions fixed for Meteor 30 | - v0.96.0 (April 1, 2015) 31 | - **Toasts, transitions, scrollfire added under Materialize namespace** 32 | - **Dropdown is now created as a child of its parent** 33 | - Collapsibles supports nesting 34 | - Modal Bottom Sheet added 35 | - Indeterminate Checkboxes added 36 | - New Checkbox Style Added 37 | - Text Inputs supports placeholder/readonly 38 | - Google Inbox-like Collapsible added 39 | - Text Character Counter added 40 | - Waves no longer breaks on SVG's 41 | 42 | - v0.95.3 (Feb 25, 2015) 43 | - Parallax image loading / responsiveness fixes 44 | - Date picker supports month/year as dropdown 45 | - Dismissable collection items 46 | - Avatar collection items 47 | - Pagination Added 48 | - ScrollFire fixes 49 | 50 | 51 | ## Contributing 52 | [Please read CONTRIBUTING.md for more information](CONTRIBUTING.md) 53 | -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/material-design-icons/LICENSE.txt: -------------------------------------------------------------------------------- 1 | https://github.com/google/material-design-icons/blob/master/LICENSE 2 | https://github.com/FezVrasta/bootstrap-material-design/blob/master/fonts/LICENSE.txt 3 | 4 | Attribution-ShareAlike 4.0 International 5 | 6 | ======================================================================= 7 | 8 | Creative Commons Corporation ("Creative Commons") is not a law firm and 9 | does not provide legal services or legal advice. Distribution of 10 | Creative Commons public licenses does not create a lawyer-client or 11 | other relationship. Creative Commons makes its licenses and related 12 | information available on an "as-is" basis. Creative Commons gives no 13 | warranties regarding its licenses, any material licensed under their 14 | terms and conditions, or any related information. Creative Commons 15 | disclaims all liability for damages resulting from their use to the 16 | fullest extent possible. 17 | 18 | Using Creative Commons Public Licenses 19 | 20 | Creative Commons public licenses provide a standard set of terms and 21 | conditions that creators and other rights holders may use to share 22 | original works of authorship and other material subject to copyright 23 | and certain other rights specified in the public license below. The 24 | following considerations are for informational purposes only, are not 25 | exhaustive, and do not form part of our licenses. 26 | 27 | Considerations for licensors: Our public licenses are 28 | intended for use by those authorized to give the public 29 | permission to use material in ways otherwise restricted by 30 | copyright and certain other rights. Our licenses are 31 | irrevocable. Licensors should read and understand the terms 32 | and conditions of the license they choose before applying it. 33 | Licensors should also secure all rights necessary before 34 | applying our licenses so that the public can reuse the 35 | material as expected. Licensors should clearly mark any 36 | material not subject to the license. This includes other CC- 37 | licensed material, or material used under an exception or 38 | limitation to copyright. More considerations for licensors: 39 | wiki.creativecommons.org/Considerations_for_licensors 40 | 41 | Considerations for the public: By using one of our public 42 | licenses, a licensor grants the public permission to use the 43 | licensed material under specified terms and conditions. If 44 | the licensor's permission is not necessary for any reason--for 45 | example, because of any applicable exception or limitation to 46 | copyright--then that use is not regulated by the license. Our 47 | licenses grant only permissions under copyright and certain 48 | other rights that a licensor has authority to grant. Use of 49 | the licensed material may still be restricted for other 50 | reasons, including because others have copyright or other 51 | rights in the material. A licensor may make special requests, 52 | such as asking that all changes be marked or described. 53 | Although not required by our licenses, you are encouraged to 54 | respect those requests where reasonable. More_considerations 55 | for the public: 56 | wiki.creativecommons.org/Considerations_for_licensees 57 | 58 | ======================================================================= 59 | 60 | Creative Commons Attribution-ShareAlike 4.0 International Public 61 | License 62 | 63 | By exercising the Licensed Rights (defined below), You accept and agree 64 | to be bound by the terms and conditions of this Creative Commons 65 | Attribution-ShareAlike 4.0 International Public License ("Public 66 | License"). To the extent this Public License may be interpreted as a 67 | contract, You are granted the Licensed Rights in consideration of Your 68 | acceptance of these terms and conditions, and the Licensor grants You 69 | such rights in consideration of benefits the Licensor receives from 70 | making the Licensed Material available under these terms and 71 | conditions. 72 | 73 | 74 | Section 1 -- Definitions. 75 | 76 | a. Adapted Material means material subject to Copyright and Similar 77 | Rights that is derived from or based upon the Licensed Material 78 | and in which the Licensed Material is translated, altered, 79 | arranged, transformed, or otherwise modified in a manner requiring 80 | permission under the Copyright and Similar Rights held by the 81 | Licensor. For purposes of this Public License, where the Licensed 82 | Material is a musical work, performance, or sound recording, 83 | Adapted Material is always produced where the Licensed Material is 84 | synched in timed relation with a moving image. 85 | 86 | b. Adapter's License means the license You apply to Your Copyright 87 | and Similar Rights in Your contributions to Adapted Material in 88 | accordance with the terms and conditions of this Public License. 89 | 90 | c. BY-SA Compatible License means a license listed at 91 | creativecommons.org/compatiblelicenses, approved by Creative 92 | Commons as essentially the equivalent of this Public License. 93 | 94 | d. Copyright and Similar Rights means copyright and/or similar rights 95 | closely related to copyright including, without limitation, 96 | performance, broadcast, sound recording, and Sui Generis Database 97 | Rights, without regard to how the rights are labeled or 98 | categorized. For purposes of this Public License, the rights 99 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 100 | Rights. 101 | 102 | e. Effective Technological Measures means those measures that, in the 103 | absence of proper authority, may not be circumvented under laws 104 | fulfilling obligations under Article 11 of the WIPO Copyright 105 | Treaty adopted on December 20, 1996, and/or similar international 106 | agreements. 107 | 108 | f. Exceptions and Limitations means fair use, fair dealing, and/or 109 | any other exception or limitation to Copyright and Similar Rights 110 | that applies to Your use of the Licensed Material. 111 | 112 | g. License Elements means the license attributes listed in the name 113 | of a Creative Commons Public License. The License Elements of this 114 | Public License are Attribution and ShareAlike. 115 | 116 | h. Licensed Material means the artistic or literary work, database, 117 | or other material to which the Licensor applied this Public 118 | License. 119 | 120 | i. Licensed Rights means the rights granted to You subject to the 121 | terms and conditions of this Public License, which are limited to 122 | all Copyright and Similar Rights that apply to Your use of the 123 | Licensed Material and that the Licensor has authority to license. 124 | 125 | j. Licensor means the individual(s) or entity(ies) granting rights 126 | under this Public License. 127 | 128 | k. Share means to provide material to the public by any means or 129 | process that requires permission under the Licensed Rights, such 130 | as reproduction, public display, public performance, distribution, 131 | dissemination, communication, or importation, and to make material 132 | available to the public including in ways that members of the 133 | public may access the material from a place and at a time 134 | individually chosen by them. 135 | 136 | l. Sui Generis Database Rights means rights other than copyright 137 | resulting from Directive 96/9/EC of the European Parliament and of 138 | the Council of 11 March 1996 on the legal protection of databases, 139 | as amended and/or succeeded, as well as other essentially 140 | equivalent rights anywhere in the world. 141 | 142 | m. You means the individual or entity exercising the Licensed Rights 143 | under this Public License. Your has a corresponding meaning. 144 | 145 | 146 | Section 2 -- Scope. 147 | 148 | a. License grant. 149 | 150 | 1. Subject to the terms and conditions of this Public License, 151 | the Licensor hereby grants You a worldwide, royalty-free, 152 | non-sublicensable, non-exclusive, irrevocable license to 153 | exercise the Licensed Rights in the Licensed Material to: 154 | 155 | a. reproduce and Share the Licensed Material, in whole or 156 | in part; and 157 | 158 | b. produce, reproduce, and Share Adapted Material. 159 | 160 | 2. Exceptions and Limitations. For the avoidance of doubt, where 161 | Exceptions and Limitations apply to Your use, this Public 162 | License does not apply, and You do not need to comply with 163 | its terms and conditions. 164 | 165 | 3. Term. The term of this Public License is specified in Section 166 | 6(a). 167 | 168 | 4. Media and formats; technical modifications allowed. The 169 | Licensor authorizes You to exercise the Licensed Rights in 170 | all media and formats whether now known or hereafter created, 171 | and to make technical modifications necessary to do so. The 172 | Licensor waives and/or agrees not to assert any right or 173 | authority to forbid You from making technical modifications 174 | necessary to exercise the Licensed Rights, including 175 | technical modifications necessary to circumvent Effective 176 | Technological Measures. For purposes of this Public License, 177 | simply making modifications authorized by this Section 2(a) 178 | (4) never produces Adapted Material. 179 | 180 | 5. Downstream recipients. 181 | 182 | a. Offer from the Licensor -- Licensed Material. Every 183 | recipient of the Licensed Material automatically 184 | receives an offer from the Licensor to exercise the 185 | Licensed Rights under the terms and conditions of this 186 | Public License. 187 | 188 | b. Additional offer from the Licensor -- Adapted Material. 189 | Every recipient of Adapted Material from You 190 | automatically receives an offer from the Licensor to 191 | exercise the Licensed Rights in the Adapted Material 192 | under the conditions of the Adapter's License You apply. 193 | 194 | c. No downstream restrictions. You may not offer or impose 195 | any additional or different terms or conditions on, or 196 | apply any Effective Technological Measures to, the 197 | Licensed Material if doing so restricts exercise of the 198 | Licensed Rights by any recipient of the Licensed 199 | Material. 200 | 201 | 6. No endorsement. Nothing in this Public License constitutes or 202 | may be construed as permission to assert or imply that You 203 | are, or that Your use of the Licensed Material is, connected 204 | with, or sponsored, endorsed, or granted official status by, 205 | the Licensor or others designated to receive attribution as 206 | provided in Section 3(a)(1)(A)(i). 207 | 208 | b. Other rights. 209 | 210 | 1. Moral rights, such as the right of integrity, are not 211 | licensed under this Public License, nor are publicity, 212 | privacy, and/or other similar personality rights; however, to 213 | the extent possible, the Licensor waives and/or agrees not to 214 | assert any such rights held by the Licensor to the limited 215 | extent necessary to allow You to exercise the Licensed 216 | Rights, but not otherwise. 217 | 218 | 2. Patent and trademark rights are not licensed under this 219 | Public License. 220 | 221 | 3. To the extent possible, the Licensor waives any right to 222 | collect royalties from You for the exercise of the Licensed 223 | Rights, whether directly or through a collecting society 224 | under any voluntary or waivable statutory or compulsory 225 | licensing scheme. In all other cases the Licensor expressly 226 | reserves any right to collect such royalties. 227 | 228 | 229 | Section 3 -- License Conditions. 230 | 231 | Your exercise of the Licensed Rights is expressly made subject to the 232 | following conditions. 233 | 234 | a. Attribution. 235 | 236 | 1. If You Share the Licensed Material (including in modified 237 | form), You must: 238 | 239 | a. retain the following if it is supplied by the Licensor 240 | with the Licensed Material: 241 | 242 | i. identification of the creator(s) of the Licensed 243 | Material and any others designated to receive 244 | attribution, in any reasonable manner requested by 245 | the Licensor (including by pseudonym if 246 | designated); 247 | 248 | ii. a copyright notice; 249 | 250 | iii. a notice that refers to this Public License; 251 | 252 | iv. a notice that refers to the disclaimer of 253 | warranties; 254 | 255 | v. a URI or hyperlink to the Licensed Material to the 256 | extent reasonably practicable; 257 | 258 | b. indicate if You modified the Licensed Material and 259 | retain an indication of any previous modifications; and 260 | 261 | c. indicate the Licensed Material is licensed under this 262 | Public License, and include the text of, or the URI or 263 | hyperlink to, this Public License. 264 | 265 | 2. You may satisfy the conditions in Section 3(a)(1) in any 266 | reasonable manner based on the medium, means, and context in 267 | which You Share the Licensed Material. For example, it may be 268 | reasonable to satisfy the conditions by providing a URI or 269 | hyperlink to a resource that includes the required 270 | information. 271 | 272 | 3. If requested by the Licensor, You must remove any of the 273 | information required by Section 3(a)(1)(A) to the extent 274 | reasonably practicable. 275 | 276 | b. ShareAlike. 277 | 278 | In addition to the conditions in Section 3(a), if You Share 279 | Adapted Material You produce, the following conditions also apply. 280 | 281 | 1. The Adapter's License You apply must be a Creative Commons 282 | license with the same License Elements, this version or 283 | later, or a BY-SA Compatible License. 284 | 285 | 2. You must include the text of, or the URI or hyperlink to, the 286 | Adapter's License You apply. You may satisfy this condition 287 | in any reasonable manner based on the medium, means, and 288 | context in which You Share Adapted Material. 289 | 290 | 3. You may not offer or impose any additional or different terms 291 | or conditions on, or apply any Effective Technological 292 | Measures to, Adapted Material that restrict exercise of the 293 | rights granted under the Adapter's License You apply. 294 | 295 | 296 | Section 4 -- Sui Generis Database Rights. 297 | 298 | Where the Licensed Rights include Sui Generis Database Rights that 299 | apply to Your use of the Licensed Material: 300 | 301 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 302 | to extract, reuse, reproduce, and Share all or a substantial 303 | portion of the contents of the database; 304 | 305 | b. if You include all or a substantial portion of the database 306 | contents in a database in which You have Sui Generis Database 307 | Rights, then the database in which You have Sui Generis Database 308 | Rights (but not its individual contents) is Adapted Material, 309 | 310 | including for purposes of Section 3(b); and 311 | c. You must comply with the conditions in Section 3(a) if You Share 312 | all or a substantial portion of the contents of the database. 313 | 314 | For the avoidance of doubt, this Section 4 supplements and does not 315 | replace Your obligations under this Public License where the Licensed 316 | Rights include other Copyright and Similar Rights. 317 | 318 | 319 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 320 | 321 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 322 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 323 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 324 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 325 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 326 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 327 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 328 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 329 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 330 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 331 | 332 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 333 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 334 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 335 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 336 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 337 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 338 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 339 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 340 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 341 | 342 | c. The disclaimer of warranties and limitation of liability provided 343 | above shall be interpreted in a manner that, to the extent 344 | possible, most closely approximates an absolute disclaimer and 345 | waiver of all liability. 346 | 347 | 348 | Section 6 -- Term and Termination. 349 | 350 | a. This Public License applies for the term of the Copyright and 351 | Similar Rights licensed here. However, if You fail to comply with 352 | this Public License, then Your rights under this Public License 353 | terminate automatically. 354 | 355 | b. Where Your right to use the Licensed Material has terminated under 356 | Section 6(a), it reinstates: 357 | 358 | 1. automatically as of the date the violation is cured, provided 359 | it is cured within 30 days of Your discovery of the 360 | violation; or 361 | 362 | 2. upon express reinstatement by the Licensor. 363 | 364 | For the avoidance of doubt, this Section 6(b) does not affect any 365 | right the Licensor may have to seek remedies for Your violations 366 | of this Public License. 367 | 368 | c. For the avoidance of doubt, the Licensor may also offer the 369 | Licensed Material under separate terms or conditions or stop 370 | distributing the Licensed Material at any time; however, doing so 371 | will not terminate this Public License. 372 | 373 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 374 | License. 375 | 376 | 377 | Section 7 -- Other Terms and Conditions. 378 | 379 | a. The Licensor shall not be bound by any additional or different 380 | terms or conditions communicated by You unless expressly agreed. 381 | 382 | b. Any arrangements, understandings, or agreements regarding the 383 | Licensed Material not stated herein are separate from and 384 | independent of the terms and conditions of this Public License. 385 | 386 | 387 | Section 8 -- Interpretation. 388 | 389 | a. For the avoidance of doubt, this Public License does not, and 390 | shall not be interpreted to, reduce, limit, restrict, or impose 391 | conditions on any use of the Licensed Material that could lawfully 392 | be made without permission under this Public License. 393 | 394 | b. To the extent possible, if any provision of this Public License is 395 | deemed unenforceable, it shall be automatically reformed to the 396 | minimum extent necessary to make it enforceable. If the provision 397 | cannot be reformed, it shall be severed from this Public License 398 | without affecting the enforceability of the remaining terms and 399 | conditions. 400 | 401 | c. No term or condition of this Public License will be waived and no 402 | failure to comply consented to unless expressly agreed to by the 403 | Licensor. 404 | 405 | d. Nothing in this Public License constitutes or may be interpreted 406 | as a limitation upon, or waiver of, any privileges and immunities 407 | that apply to the Licensor or You, including from the legal 408 | processes of any jurisdiction or authority. 409 | 410 | 411 | ======================================================================= 412 | 413 | Creative Commons is not a party to its public licenses. 414 | Notwithstanding, Creative Commons may elect to apply one of its public 415 | licenses to material it publishes and in those instances will be 416 | considered the "Licensor." Except for the limited purpose of indicating 417 | that material is shared under a Creative Commons public license or as 418 | otherwise permitted by the Creative Commons policies published at 419 | creativecommons.org/policies, Creative Commons does not authorize the 420 | use of the trademark "Creative Commons" or any other trademark or logo 421 | of Creative Commons without its prior written consent including, 422 | without limitation, in connection with any unauthorized modifications 423 | to any of its public licenses or any other arrangements, 424 | understandings, or agreements concerning use of licensed material. For 425 | the avoidance of doubt, this paragraph does not form part of the public 426 | licenses. 427 | 428 | Creative Commons may be contacted at creativecommons.org. 429 | -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/material-design-icons/Material-Design-Icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/material-design-icons/Material-Design-Icons.eot -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/material-design-icons/Material-Design-Icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/material-design-icons/Material-Design-Icons.ttf -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/material-design-icons/Material-Design-Icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/material-design-icons/Material-Design-Icons.woff -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/material-design-icons/Material-Design-Icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/material-design-icons/Material-Design-Icons.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /src/main/resources/static/vendor/materialize/font/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treehouse/giflib-hibernate/4cd607e5dfde420e2e07d3970f4ede13f4cff783/src/main/resources/static/vendor/materialize/font/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /src/main/resources/templates/category/details.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
    6 |
    7 |
    8 |
    9 |

    10 |
    11 |
    12 | 13 | 14 | 15 | 16 |
    17 |
    18 |
    19 |
    20 | 21 | -------------------------------------------------------------------------------- /src/main/resources/templates/category/form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
    6 |
    7 |
    8 |
    9 | 10 |
    11 |
    12 |

    New Category

    13 |
    14 |
    15 |
    16 |
    17 |
    18 | 19 |
    20 |
    21 |
    22 |
    23 |
    24 | 28 |
    29 |
    30 |
    31 |
    32 |
    33 | 34 | Cancel 35 |
    36 |
    37 |
    38 |
    39 |
    40 |
    41 | 42 |
    43 |
    44 |
    45 |
    46 |
    47 | 48 | -------------------------------------------------------------------------------- /src/main/resources/templates/category/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
    6 |
    7 |
    8 |
    9 |
    10 |
    11 |

    Categories

    12 |
    13 |
    14 | + 15 |
    16 |
    17 |
    18 |
    19 |
    20 |
    21 |
    22 | 26 |
    27 |
    28 |
    29 |
    30 |
    31 |
    32 | 33 | -------------------------------------------------------------------------------- /src/main/resources/templates/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 |
    15 |
    16 |
    17 |

    500 Error

    18 |
    19 | Error Message 20 |
    21 |
    22 |
    23 | 24 | -------------------------------------------------------------------------------- /src/main/resources/templates/gif/details.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
    6 |
    7 |
    8 |
    9 |
    10 |
    11 | gif 12 |
    13 | 14 |
    15 |
    16 |
    17 |
    18 |
    19 |

    20 |
    21 | 22 | 23 |
    24 | Edit 25 |
    26 | 27 |
    28 |
    29 |
    30 |
    31 |
    32 | avatar 33 |
    34 |
    35 |
    36 |
    37 |
    38 |
    39 |
    40 | 44 |
    45 |
    46 |
    47 |
    48 | 49 | -------------------------------------------------------------------------------- /src/main/resources/templates/gif/favorites.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
    6 |
    7 |
    8 |
    9 |
    10 | avatar 11 |
    12 |
    13 |

    Jane Doe

    14 |
    15 |
    16 |
    17 |
    18 |
    19 |
    20 |
    21 | 22 | 23 | 24 | 25 |
    26 |
    27 |
    28 |
    29 | 30 | -------------------------------------------------------------------------------- /src/main/resources/templates/gif/form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
    6 |
    7 |
    8 |
    9 |
    10 |

    Upload

    11 |
    Upload and share your GIFs with friends and family on Facebook, Twitter, and everywhere else.
    12 |
    13 |
    14 |
    15 |
    16 | 17 |
    18 |
    19 |
    20 | 21 | Choose an image... 22 | 23 |
    24 |
    25 |
    26 |
    27 |
    28 | 29 |
    30 |
    31 |
    32 |
    33 | 37 |
    38 |
    39 |
    40 |
    41 | 42 | Cancel 43 |
    44 |
    45 |
    46 |
    47 |
    48 | 49 | -------------------------------------------------------------------------------- /src/main/resources/templates/gif/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
    6 |
    7 |
    8 |
    9 |
    10 | 18 |
    19 |
    20 |
    21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | giflib 17 | 18 | 19 | 20 | 21 | 41 | 42 | 54 | 55 |
    56 | close 57 |
    58 |
    59 | 60 |
    61 | 62 | 63 | 64 | 65 | 66 |
    67 | 68 | 69 | --------------------------------------------------------------------------------