diff --git a/daegsrv/.gitignore b/daegsrv/.gitignore new file mode 100644 index 0000000..38566a4 --- /dev/null +++ b/daegsrv/.gitignore @@ -0,0 +1,26 @@ +# Database +local_db + +# Local server configuration +local + +# Eliom +.depend +_client +_deps +_server + +# Mobile application +cordova + +# Style +.sass-cache + +static/css/daegsrv.css.map +static/css/daegsrv.css + +# NPM +node_modules + +# Make +Makefile.local diff --git a/daegsrv/.ocamlformat b/daegsrv/.ocamlformat new file mode 100644 index 0000000..ee092cc --- /dev/null +++ b/daegsrv/.ocamlformat @@ -0,0 +1,31 @@ +break-cases = fit +break-collection-expressions = fit-or-vertical +break-fun-decl = wrap +break-fun-sig = wrap +break-infix = wrap +break-infix-before-func = false +break-sequences = false +break-separators = before +break-string-literals = never +break-struct = force +cases-matching-exp-indent = compact +doc-comments = after-when-possible +dock-collection-brackets = false +indicate-multiline-delimiters = no +infix-precedence = indent +let-and = compact +let-binding-spacing = compact +module-item-spacing = compact +parens-tuple = multi-line-only +parens-tuple-patterns = multi-line-only +sequence-style = terminator +sequence-blank-line = compact +single-case = compact +type-decl = compact +if-then-else = keyword-first +field-space = loose +space-around-arrays = false +space-around-records = false +space-around-lists = false +space-around-variants = false +ocp-indent-compat = true diff --git a/daegsrv/.ocp-indent b/daegsrv/.ocp-indent new file mode 100644 index 0000000..e98972d --- /dev/null +++ b/daegsrv/.ocp-indent @@ -0,0 +1,4 @@ +normal +with=0 +syntax=lwt mll +max_indent=2 diff --git a/daegsrv/Makefile b/daegsrv/Makefile new file mode 100644 index 0000000..d0e500c --- /dev/null +++ b/daegsrv/Makefile @@ -0,0 +1,26 @@ +#---------------------------------------------------------------------- +# GLOBAL MAKEFILE +#---------------------------------------------------------------------- + +# Local settings (optional). See Makefile.local.example for an example. +# WARNING: do not commit to a repository! +-include Makefile.local + +# Eliom settings. Contains all variables. +include Makefile.options + +# I18N rules. +include Makefile.i18n + +# Database rules. +include Makefile.db + +# Styles (CSS, SASS...). +include Makefile.style + +# Ocsigen-start makefile +# Makefile.PROJECT_NAME is also included in this makefile +include Makefile.os + +# Mobile app makefile +include Makefile.mobile diff --git a/daegsrv/Makefile.daegsrv b/daegsrv/Makefile.daegsrv new file mode 100644 index 0000000..36ec9dc --- /dev/null +++ b/daegsrv/Makefile.daegsrv @@ -0,0 +1,10 @@ +#---------------------------------------------------------------------- +# SETTINGS FOR THE ELIOM PROJECT daegsrv +#---------------------------------------------------------------------- + +## Here you can add specific rules for your project. It's recommended to add +## variables in Makefile.options (which is supposed to contain all variables and +## no rules). + +## This file is included in Makefile.os. You can also use any variable defined +## in Makefile.options. diff --git a/daegsrv/Makefile.db b/daegsrv/Makefile.db new file mode 100644 index 0000000..de98009 --- /dev/null +++ b/daegsrv/Makefile.db @@ -0,0 +1,152 @@ +## --------------------------------------------------------------------- +## This Makefile contains the rules about the database management. +## +## Ocsigen Start uses PostgreSQL so you need to install it. +## +## Ocsigen Start uses pg_ctl. If this executable is not found, an error occurs. +## +## Some variables like the PostgreSQL directory for log, the PostgreSQL +## directory or the variables to access the database (like the database name or +## the user) can be found and changed in the file Makefile.options + +## --------------------------------------------------------------------- +## The following part defines some variables by adding the environment variables +## to the used binaries. + +export PGHOST := $(DB_HOST) +export PGDATABASE := $(DB_NAME) +export PGPORT := $(DB_PORT) +export PGUSER := $(DB_USER) +export PGPASSWORD := $(DB_PASSWORD) + +# Ocsigenserver uses the DB settings from daegsrv.conf.in (which +# should be the same as the exported variables above) +OCSIGENSERVER := $(OCSIGENSERVER) +OCSIGENSERVER.OPT := $(OCSIGENSERVER.OPT) + +OPAM_LIB_DIR := $(shell opam config var lib) +OS_UPGRADE_FILE := $(OPAM_LIB_DIR)/eliom/templates/os.pgocaml/upgrade.sql +## --------------------------------------------------------------------- + +##---------------------------------------------------------------------- + +pg_dump = pg_dump + +# Rule to get the pg_ctl binary. +ifeq ($(shell psql --version 2> /dev/null),) +$(error "PostgreSQL is not installed") +else +pg_ctl = $(shell which pg_ctl || \ + ls /usr/lib/postgresql/*/bin/pg_ctl | \ + sort -nr -t / -k 5 | head -n 1) +endif + +##---------------------------------------------------------------------- + +## --------------------------------------------------------------------- +## Here some rules to easily manage the database. +# - db-init: +# Initialise the database. It creates the directory PSQL_DIR and +# start the database. +# +# - db-start: +# Start the database. +# +# - db-stop: +# Stop the database. +# +# - db-status: +# Get the database status. +# +# - db-create: +# Create the database and use UNICODE. +# +# - db-schema: +# Execute the SQL file contained in the PSQL_FILE +# variable to create the schema and the tables. By default, the PSQL_FILE is +# PROJECTNAME.sql. See Makefile.options. +# +# - db-upgrade: +# Upgrade the database schema related to Ocsigen Start to the newer database +# schema. +# It uses $(OS_UPGRADE_FILE) SQL file. You must not change it. +# Please read the upgrade information before calling this rule. +# Use it carefully! +# +# - db-drop: +# Drop the database but doesn't remove the database directory PSQL_DIR. +# +# - db-psql: +# Connect to the database. +# +# - db-delete: +# Stop the database (without error if it's not running) and remove +# the database directory containing all database data and the log file. +# +# Depending on the value of the LOCAL variable (defined in Makefile.options), +# the database is created locally or globally. By default, the database is +# local. + +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- + +$(PSQL_DIR): + -mkdir -p $@ + +ifeq ($(LOCAL),yes) + +db-init: $(PSQL_DIR) + $(pg_ctl) initdb -o --encoding=UNICODE -D $(PSQL_DIR) + echo unix_socket_directories = \'/tmp\' >> $(PSQL_DIR)/postgresql.conf + $(pg_ctl) -o "-p $(DB_PORT)" -D $(PSQL_DIR) -l $(PSQL_LOG) start + +db-start: + $(pg_ctl) -o "-p $(DB_PORT)" -D $(PSQL_DIR) -l $(PSQL_LOG) start + +db-stop: + $(pg_ctl) -D $(PSQL_DIR) -l $(PSQL_LOG) stop + +db-status: + $(pg_ctl) -D $(PSQL_DIR) -l $(PSQL_LOG) status + +db-delete: + $(pg_ctl) -D $(PSQL_DIR) -l $(PSQL_LOG) stop || true + rm -f $(PSQL_LOG) + rm -rf $(PSQL_DIR) + +db-snapshot: + @echo "# Creating $(DB_SNAPSHOT)" + $(pg_dump) --clean --create --no-owner --encoding=utf8 \ + $(DB_NAME) | gzip > $(DB_SNAPSHOT) + +else + +db-start: + $(pg_ctl) -o "-p $(DB_PORT)" start + +db-status: + $(pg_ctl) status + +db-stop: + $(pg_ctl) stop + +endif + +db-create: + createdb --encoding UNICODE $(DB_NAME) + +db-schema: + psql -d $(DB_NAME) -f $(PSQL_FILE) + +db-upgrade: + psql -d $(DB_NAME) -f $(OS_UPGRADE_FILE) + +db-drop: + dropdb $(DB_NAME) + dropdb ocsipersist_daegsrv + +db-psql: + psql $(DB_NAME) + +##---------------------------------------------------------------------- diff --git a/daegsrv/Makefile.i18n b/daegsrv/Makefile.i18n new file mode 100644 index 0000000..6fccf7e --- /dev/null +++ b/daegsrv/Makefile.i18n @@ -0,0 +1,47 @@ +##---------------------------------------------------------------------- +## The following part defines rules for i18n. +## See https://github.com/besport/ocsigen-i18n for more information. + +I18N_CHECKER := ocsigen-i18n-checker +I18N_GENERATOR := ocsigen-i18n-generator + +## The i18n generated file. +## IMPROVEME: Due to daegsrv_language, the module +## defining all translations must be static. +I18N_ELIOM_FILE := $(PROJECT_NAME)_i18n.eliom + +## PPX extension to rewrite each file while compiling. +I18N_PPX_REWRITER := "ocsigen-i18n-rewriter --prefix 'Daegsrv_' --suffix '_i18n' --default-module Daegsrv_i18n" + +## This rule will update the primary I18N module +$(I18N_ELIOM_FILE): $(I18N_PRIMARY_FILE) +# use LC_ALL=C so that all $(I18N_ELIOM_FILE) files are generated the same +# way. + LC_ALL=C $(I18N_GENERATOR) \ + --languages $(I18N_LANGUAGES) \ + --default-language $(I18N_DEFAULT_LANGUAGE) \ + < $(I18N_PRIMARY_FILE) \ + > $(I18N_ELIOM_FILE) + +## This rule will update all other I18N modules +$(PROJECT_NAME)_%_i18n.eliom: $(I18N_TSV_DIR)$(PROJECT_NAME)_%_i18n.tsv +# use LC_ALL=C so that all I18N files are generated the same +# way. + LC_ALL=C $(I18N_GENERATOR) \ + --languages $(I18N_LANGUAGES) \ + --default-language $(I18N_DEFAULT_LANGUAGE) \ + --primary $(notdir $(I18N_PRIMARY_FILE)) \ + < $< \ + > $@ + + +i18n-check: + $(I18N_CHECKER) ./*.eliom < $(I18N_TSV_FILE) + +.PHONY: i18n-clean +i18n-clean: + -rm -f $(notdir $(patsubst %.tsv,%.eliom,$(I18N_PRIMARY_FILE) \ + $(I18N_EXTRA_FILES))) + +## end of i18n +##---------------------------------------------------------------------- diff --git a/daegsrv/Makefile.local.example b/daegsrv/Makefile.local.example new file mode 100644 index 0000000..8eaab3a --- /dev/null +++ b/daegsrv/Makefile.local.example @@ -0,0 +1,18 @@ +## This is an example of a Makefile.local file. It contains some example of +## variables you can define. +## +## Makefile.local must be only local: you must not distribute it. It must +## contain the local configuration for your development workflow. + +# The URL address of your Web app (without / at the end). +# See Makefile.mobile for more information about this variable. +APP_SERVER := http://YOUR_IP_ADDRESS:PORT + +# Use to decide if JS and CSS files must be retrieved from the server or not +# while building the mobile application. +# See Makefile.mobile for more information about this variable. +APP_REMOTE := yes + +# Use to declare the app as a dev build that should access cleartext data +# over `http`. A release should be anything else than `dev`. +APP := dev diff --git a/daegsrv/Makefile.mobile b/daegsrv/Makefile.mobile new file mode 100644 index 0000000..7f8bb28 --- /dev/null +++ b/daegsrv/Makefile.mobile @@ -0,0 +1,466 @@ +#---------------------------------------------------------------------- +# This Makefile contains rules about the mobile application. +#---------------------------------------------------------------------- + +WWW_PATH := local/var/www/$(PROJECT_NAME) + +mobile-all: assets android browser # ios + +##---------------------------------------------------------------------- +## Errors definition + +define ERROR_APP_SERVER + +Error: APP_SERVER environment is not set! + +You need to define the environment variable APP_SERVER in order to build the +mobile app. This server is the one your mobile app is connecting to and should +be running when building the app, so the matching Eliom JS and CSS files can be +retrieved. + +Example: +$$ make APP_SERVER=http://eliomapp.example.com APP_REMOTE=yes android + +endef + +define ERROR_APP_REMOTE + +Error: APP_REMOTE environment is not set! + +You need to define the environment variable APP_REMOTE to 'yes' or 'no' in +order to build the mobile app. If set to 'yes', JS and CSS files will be +retrieved from the remote server defined in APP_SERVER; if set to 'no', locally +generated ones will be used instead. + +Example: +$$ make APP_REMOTE=yes APP_SERVER=http://eliomapp.example.com android + +endef + +define ERROR_DOWNLOAD_JS + +Error: Downloading of Eliom JS file from server $(APP_SERVER) has failed. +Please check that $(APP_SERVER) is running properly and try again. + +endef + +define ERROR_DOWNLOAD_CSS + +Error: Downloading of Eliom CSS file from server $(APP_SERVER) has failed. +Please check that $(APP_SERVER) is running properly and try again. + +endef + +##---------------------------------------------------------------------- +## Warnings definition + +define WARNING_NO_CORDOVA + +WARNING: 'cordova' command not found in PATH. Assuming we don't need it for the +current build. It makes sense if you only need Hot Code Push update files on a +server, for example. If you want to build the actual app, you need to install +Cordova. See the documentation at the beginning of 'Makefile.mobile' for +detailed instructions. + +endef + + +##---------------------------------------------------------------------- +## Environment variables definition. + +APPJS := $(CORDOVAPATH)/www/$(PROJECT_NAME).js +APPCSS := $(CORDOVAPATH)/www/css/$(PROJECT_NAME).css + +##---------------------------------------------------------------------- +## Mobile app related ## variables checking + +check-app-env: +ifeq ($(strip $(APP_SERVER)),) + $(error $(ERROR_APP_SERVER)) +endif +ifeq ($(strip $(APP_REMOTE)),) + $(error $(ERROR_APP_REMOTE)) +endif + +# Rules to get the JS and CSS files when building. If APP_REMOTE is set to yes, +# the JS and CSS files are retrieved from APP_SERVER. Else, it copies the local +# JS and CSS files generated during the server compilation. + +$(APPJS): $(JS_PREFIX).js +ifeq ($(APP_REMOTE),yes) + APPJS_FILE=$$(curl -s -f $(APP_SERVER)$(APP_PATH)/ | cat | grep -E -o -m 1 '$(PROJECT_NAME)_[a-f0-9]*?\.js') &&\ + curl -s -o $@ $(APP_SERVER)$(APP_PATH)/$$APPJS_FILE +else + cp -f $(WWW_PATH)/`readlink $(JS_PREFIX).js` $@ +endif + +$(APPCSS): +ifeq ($(APP_REMOTE),yes) + APPCSS_FILE=$$(curl -s -f $(APP_SERVER)$(APP_PATH)/ | cat | grep -E -o -m 1 '$(PROJECT_NAME)_[a-f0-9]*?\.css') &&\ + curl -s -o $@ $(APP_SERVER)$(APP_PATH)/css/$$APPCSS_FILE +else + cp -f $(WWW_PATH)/css/`readlink $(CSS_PREFIX).css` $@ +endif + +##---------------------------------------------------------------------- +## Regenerate config files at each build, in case an environment variable has +## changed. +.PHONY: clean \ + icons spashes $(IOS_SPLASHES) \ + chcp app-config check-app-env \ + $(APPJS) $(APPCSS) \ + $(ADD_PLATFORMS) $(PLATFORMS) $(EMULATE_PLATFORMS) $(RUN_PLATFORMS) \ + $(CORDOVAPATH)/www/chcp.json \ + $(CORDOVAPATH)/www/eliom.html \ + $(CORDOVAPATH)/www/index.html \ + $(CORDOVAPATH)/config.xml + +ifeq ($(USE_NPM), yes) +CORDOVA = PATH=$(PWD)/node_modules/cordova/bin:$$PATH cordova +CORDOVA_HCP = PATH=$(PWD)/node_modules/cordova-hot-code-push-cli/bin:$$PATH cordova-hcp +else +CORDOVA = cordova +CORDOVA_HCP = cordova-hcp +endif + +# Necessary Cordova and static files directories. They are created when +# creating the Cordova project. +CORDOVA_DIRS := www www/css www/fonts www/images www/img www/js + +# If Cordova is installed, use it to create a new project +# If not, we assume we don't need a full fledge project and simply create +# necessary directories (typically on a server where we only want to set up +# Hot Code Push). +# The CSS, JS, logo, index and config.xml created by the Cordova CLI are removed +# to be replaced later. +$(CORDOVAPATH): +ifeq ($(USE_NPM), yes) + npm install cordova git+https://github.com/besport/cordova-hot-code-push-cli.git read-package-json xml2js +endif + $(CORDOVA) create $(CORDOVAPATH) $(MOBILE_APP_ID) "$(MOBILE_APP_NAME)" + rm -f $(CORDOVAPATH)/www/css/index.css + rm -f $(CORDOVAPATH)/www/js/index.js + rm -f $(CORDOVAPATH)/www/img/logo.png + rm -f $(CORDOVAPATH)/www/index.html + rm -f $(CORDOVAPATH)/config.xml + $(foreach dir,$(CORDOVA_DIRS),mkdir -p $@/$(dir);) + +##---------------------------------------------------------------------- +## Rules for static files. + +# Function to list files in a given directory $(1), and change prefix directory +# to the target one $(2) +# Example: $(call static_targets,$(SOURCE_DIR),$(TARGET_DIR)) +static_targets = $(patsubst $(1)%,$(2)%,$(shell find $(1) -type f | grep -v .well-known | grep -v "/fonts/.*\\([.]svg\\|[.]eot\\|webfont[.]ttf\\)$$")) + +# Cordova static files which needs to be copied to the Cordova project +# directory. +CORDOVA_STATIC_FILES := \ + $(CORDOVAPATH)/.chcpignore \ + $(call static_targets,$(MOBILESTATICPATH)/www,$(CORDOVAPATH)/www) + +LOCAL_STATIC_FILES := $(call static_targets,$(LOCAL_STATIC)/css,$(CORDOVAPATH)/www/css) $(call static_targets,$(LOCAL_STATIC)/images,$(CORDOVAPATH)/www/images) $(call static_targets,$(LOCAL_STATIC)/fonts,$(CORDOVAPATH)/www/fonts) + +# Static files dependencies: if a file changes in these directory, a new copy +# of static files will be triggered +# The rules related to static files are essentially copy in the appropriate +# mobile directory. +$(CORDOVA_STATIC_FILES): $(CORDOVAPATH)/%: $(MOBILESTATICPATH)/% + cp -rf $< $@ + +$(LOCAL_STATIC_FILES): $(CORDOVAPATH)/www/%: $(LOCAL_STATIC)/% + cp -rf $< $@ + + +##---------------------------------------------------------------------- +## Rules for cordova config files like index.html, eliom_loader.js, etc. + +# Parse APP_SERVER and extract host, scheme, port. A bit rudimentary, +# but it should work for URLs starting with http:// or https://. + +APP_SCHEME = $(shell echo $(APP_SERVER) | cut -d : -f 1) +APP_HOST = $(shell echo $(APP_SERVER) | cut -d / -f 3 | cut -d : -f 1) +APP_PORT = $(shell echo $(APP_SERVER) | cut -d / -f 3 | cut -d : -f 2) + +ifneq ($(APP_PORT),"") +APP_PORT_ARG="port=\"$(APP_PORT)\"" +else +APP_PORT_ARG= +endif + +ifeq ($(APP),dev) + MOBILE_USE_CLEARTEXT_TRAFFIC?= +else + MOBILE_USE_CLEARTEXT_TRAFFIC?= +endif + +# This rule generates the config.xml file from mobile/config.xml.in. +$(CORDOVAPATH)/config.xml: mobile/config.xml.in $(CORDOVAPATH) + sed -e "s,%%APPSERVER%%,$(APP_SERVER),g" \ + -e "s,%%APPPATH%%,$(APP_PATH),g" \ + -e "s,%%APPHOST%%,$(APP_HOST),g" \ + -e "s,%%APPPORTARG%%,$(APP_PORT_ARG),g" \ + -e "s,%%APPSCHEME%%,$(APP_SCHEME),g" \ + -e "s,%%APPID%%,$(MOBILE_APP_ID),g" \ + -e "s,%%MOBILE_APP_NAME%%,$(MOBILE_APP_NAME),g" \ + -e "s,%%MOBILE_APP_VERSION%%,$(MOBILE_APP_VERSION),g" \ + -e "s,%%MOBILE_DESCRIPTION%%,$(MOBILE_DESCRIPTION),g" \ + -e "s,%%MOBILE_AUTHOR_EMAIL%%,$(MOBILE_AUTHOR_EMAIL),g" \ + -e "s,%%MOBILE_AUTHOR_HREF%%,$(MOBILE_AUTHOR_HREF),g" \ + -e "s,%%MOBILE_AUTHOR_DESCRIPTION%%,$(MOBILE_AUTHOR_DESCRIPTION),g" \ + -e "s,%%MOBILE_ANDROID_SDK_VERSION%%,$(MOBILE_ANDROID_SDK_VERSION),g" \ + -e "s,%%MOBILE_NOTIFICATIONS_SENDER_ID%%,$(MOBILE_NOTIFICATIONS_SENDER_ID),g" \ + -e "s,%%MOBILE_USE_CLEARTEXT_TRAFFIC%%,$(MOBILE_USE_CLEARTEXT_TRAFFIC),g" \ + mobile/config.xml.in > $@ + +# This rule generates index.html. md5sum is used to set the right JavaScript +# filename in the page. +$(CORDOVAPATH)/www/index.html: $(CORDOVAPATH) $(APPJS) mobile/index.html.in + HASH=$$(md5sum $(APPJS) | cut -d ' ' -f 1) && \ + sed -e "s,%%APPNAME%%,$(PROJECT_NAME)_$$HASH,g" \ + -e "s,%%APPSERVER%%,$(APP_SERVER),g" \ + -e "s,%%APPPATH%%,$(APP_PATH),g" \ + -e "s,%%MOBILE_APP_NAME%%,$(MOBILE_APP_NAME),g" \ + mobile/index.html.in > \ + $(CORDOVAPATH)/www/index.html + +# This rule generates eliom.html. md5sum is used to set the right JavaScript and +# CSS filenames in the page. +$(CORDOVAPATH)/www/eliom.html: $(CORDOVAPATH) \ + $(APPJS) $(APPCSS) mobile/eliom.html.in + JS_HASH=$$(md5sum $(APPJS) | cut -d ' ' -f 1) && \ + CSS_HASH=$$(md5sum $(APPCSS) | cut -d ' ' -f 1) && \ + sed -e "s,%%APPNAME%%,$(PROJECT_NAME)_$$JS_HASH,g" \ + -e "s,%%APPPATH%%,$(APP_PATH),g" \ + -e "s,%%PROJECTNAME%%,$(PROJECT_NAME),g" \ + -e "s,%%APPSERVER%%,$(APP_SERVER),g" \ + mobile/eliom.html.in > $@ + +# Eliom loader is used in the index.html to retrieve update from the server. +mobile/eliom_loader.byte: mobile/eliom_loader.ml + ocamlfind ocamlc \ + -package js_of_ocaml,js_of_ocaml-ppx,js_of_ocaml-lwt,lwt_ppx \ + -linkpkg -o mobile/eliom_loader.byte \ + $< + +$(CORDOVAPATH)/www/eliom_loader.js: mobile/eliom_loader.byte + js_of_ocaml $< -o $@ + +# app-config builds all needed files by the mobile application. +app-config: $(CORDOVAPATH)/www/index.html \ + $(CORDOVAPATH)/www/eliom.html \ + $(CORDOVAPATH)/www/eliom_loader.js \ + $(CORDOVAPATH)/config.xml \ + $(CORDOVAPATH)/www/chcp.manifest \ + $(CORDOVAPATH)/www/chcp.json + +##---------------------------------------------------------------------- +## Rules to update the mobile applications. For the moment, Cordova Hot Code +## Push is used. + +# Get the actual timestamp which will be used by chcp.json. +TIMESTAMP := $(shell date +%y%m%d-%H%M%S) + +# Build the chcp.manifest with cordova-hcp build for updates. First, it updates +# and copies all files for the mobile application. +$(CORDOVAPATH)/www/chcp.manifest: $(APPJS) $(APPCSS) \ + $(CORDOVAPATH) $(CORDOVA_STATIC_FILES) \ + $(LOCAL_STATIC_FILES) + cd $(CORDOVAPATH) ; $(CORDOVA_HCP) build + $(RM) $(CORDOVAPATH)/www/chcp.json + +# Build the chcp.json based on mobile/chcp.json.in after creating the +# chcp.manifest. +$(CORDOVAPATH)/www/chcp.json: mobile/chcp.json.in \ + $(CORDOVAPATH)/www/chcp.manifest + sed -e "s,%%APPSERVER%%,$(APP_SERVER),g" \ + -e "s,%%APPPATH%%,$(APP_PATH),g" \ + -e "s,%%DATE%%,$(TIMESTAMP),g" \ + $< > $@ + +# Create the $(TIMESTAMP) directory where updates will be copied. First, it +# needs to generate and copy all files needed by the mobile application. +$(WWW_PATH)/update/$(TIMESTAMP): app-config check-app-env \ + $(CORDOVA_STATIC_FILES) $(LOCAL_STATIC_FILES) + mkdir -p $(WWW_PATH)/update + cp -r $(CORDOVAPATH)/www $@ + +# Build the chcp configuration based on the timestamp. First, it calls the rule +# to create the update/TIMESTAMP directory where updated files will be copied +# and secondly, it copies the chcp.json generated previously in the update/conf +# directory. +$(WWW_PATH)/update/conf/chcp.json: $(WWW_PATH)/update/$(TIMESTAMP) + mkdir -p $(WWW_PATH)/update/conf + cp $ $(INSTALL_PREFIX)$(CMDPIPE) +CMDPIPE := var/run/${PROJECT_NAME}-cmd + +# Ocsigenserver's logging files +LOGDIR := var/log/${PROJECT_NAME} + +# Ocsigenserver's persistent data files +DATADIR := var/data/${PROJECT_NAME} + +# Project's static files +FILESDIR := var/www/${PROJECT_NAME} + +# Project's JavaScript file directory +ELIOMSTATICDIR := var/www/${PROJECT_NAME} + +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## The following part contains the settings for CSS. + +# Directory with files to be statically served. All external CSS files will be +# copied in this directory. +LOCAL_STATIC := static +LOCAL_STATIC_CSS := $(LOCAL_STATIC)/css +LOCAL_STATIC_IMAGES := $(LOCAL_STATIC)/images +LOCAL_STATIC_FONTS := $(LOCAL_STATIC)/fonts +# Default CSS if not using SASS: +LOCAL_STATIC_DEFAULTCSS := $(LOCAL_STATIC)/defaultcss + +# Main CSS generated file. +# If you use SASS, this is the compiled file. +# If you don't use SASS, all CSS files in $(CSS_FILES) will be concatenate in +# this file. +LOCAL_CSS := $(LOCAL_STATIC_DEFAULTCSS)/$(PROJECT_NAME).css + +# The OPAM share directory. +SHAREDIR := $(shell $(OPAM) config var share) + +# The ocsigen-toolkit directory containing CSS files. +SHAREDIR_OT_CSS := $(SHAREDIR)/ocsigen-toolkit/css + +# CSS files to copy from other projects into the working directory. +# By default, ocsigen-start needs the CSS files of ocsigen-toolkit. +# If you don't need ocsigen-toolkit CSS files, remote CSS files related to +# ocsigen-toolkit in this variable. +# The order is important because it will be used when concataining all CSS files +# if SASS is deactivated. +# These files are copied with every run of make so they must not be modified. +EXTERNAL_CSS_FILES := $(SHAREDIR_OT_CSS)/ot_buttons.css \ + $(SHAREDIR_OT_CSS)/ot_carousel.css \ + $(SHAREDIR_OT_CSS)/ot_tongue.css \ + $(SHAREDIR_OT_CSS)/ot_sticky.css \ + $(SHAREDIR_OT_CSS)/ot_datetime.css \ + $(SHAREDIR_OT_CSS)/ot_drawer.css \ + $(SHAREDIR_OT_CSS)/ot_icons.css \ + $(SHAREDIR_OT_CSS)/ot_picture_uploader.css \ + $(SHAREDIR_OT_CSS)/ot_popup.css \ + $(SHAREDIR_OT_CSS)/ot_spinner.css \ + $(SHAREDIR_OT_CSS)/ot_page_transition.css + +# All CSS files which must be concatenated. +# This variable is only used if SASS is deactivated. +# The order is important because it will be used when concataining all CSS +# files. +CSS_FILES := $(LOCAL_STATIC_CSS)/font-awesome.min.css \ + $(EXTERNAL_CSS_FILES) \ + $(LOCAL_STATIC_DEFAULTCSS)/os.css +# CSS destination +CSSDIR := $(TEST_PREFIX)$(ELIOMSTATICDIR)/css +CSS_PREFIX := $(CSSDIR)/$(PROJECT_NAME) +CSS_DEST := $(CSS_PREFIX).css +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## The following part contains the settings for SASS. + +# Set to yes if you want to use SASS (http://sass-lang.com). Any other value +# will disable SASS. +# It is recommended to use SASS to write your styles. By default, SASS is +# activated. +USE_SASS := yes + +# The directory where SASS files are. +SASSDIR := sass + +# The filename of the main SASS file. +# You can import other stylesheets from this file +# (with @import "path/filename[.css]"). +SASS_SRC := $(SASSDIR)/$(PROJECT_NAME).scss + +# A separated-colon list of path containing CSS and SCSS files. It allows to +# avoid to write the entire path to a SCSS/CSS file in a @import rule. +SASS_PATH := $(SASSDIR)/lib:$(LOCAL_STATIC_CSS) +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## The following part contains the settings for debugging + +# Debug application (yes/no): Debugging info in compilation, +# JavaScript, ocsigenserver +DEBUG := yes + +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## The following part contains the settings for the mobile application. + +# The directory where the Cordova application will be copied. +CORDOVAPATH := cordova + +# The directory containing the files about the mobile application like the +# config.xml.in, index.html.in, eliom_loader.ml, ... +MOBILESTATICPATH := mobile + +MOBILE_APP_ID := com.daegsrv.mobile +MOBILE_APP_NAME := daegsrv +MOBILE_APP_VERSION := 0.1 +MOBILE_DESCRIPTION := daegsrv mobile application +MOBILE_AUTHOR_EMAIL := developer@domain.com +MOBILE_AUTHOR_HREF := http://domain.com +MOBILE_AUTHOR_DESCRIPTION := daegsrv team +MOBILE_ANDROID_SDK_VERSION := 19 +MOBILE_NOTIFICATIONS_SENDER_ID := 424242424242 + +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## The following part is only used for PGOcaml (ie for the database). + +## The host database. +DB_HOST := localhost + +## The port of the database server +DB_PORT := 3000 + +## The database name for the project. By default, it's the project name. +DB_NAME := daegsrv + +## The database user. By default, it will use the username of the current user. +DB_USER := $(USER) + +## The password to access the database. By default it's empty. +DB_PASSWORD := "" + +## The main SQL file. This file will be used by the rule 'db-schema' (defined +## in Makefile.db) to initialise the database schema. +PSQL_FILE := $(DB_NAME).sql + +## The filename for the database snapshot. This variable is used +## by 'db-snapshot'. +DB_SNAPSHOT := daegsrv-$$(date '+%Y%m%d%H%M%S').sql.gz + +## Choose if the database will be installed locally or globally +# - yes: will create the database in the $(TEST_PREFIX)/db (which has the value +# 'local' by default). +# - no: will use the default database daemon +LOCAL := yes + +## If the LOCAL variable is set to yes, PSQL_DIR is the database directory. +PSQL_DIR := local_db + +## If the LOCAL variable is set to yes, PSQL_LOG is the log directory. +PSQL_LOG := $(PSQL_DIR)/log + +##---------------------------------------------------------------------- diff --git a/daegsrv/Makefile.os b/daegsrv/Makefile.os new file mode 100644 index 0000000..19e9151 --- /dev/null +++ b/daegsrv/Makefile.os @@ -0,0 +1,350 @@ +#---------------------------------------------------------------------- +# OCSIGEN-START MAKEFILE, NOT TO BE MODIFIED +#---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## DISCLAIMER +## +## This file contains the rules to make an Eliom project. The project is +## configured through the variables in the file Makefile.options. +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## Internals + +## Required binaries +ELIOMC := eliomc -w +A-4-7-9-37-38-39-41-42-44-45-48-70 +ELIOMOPT := eliomopt +JS_OF_ELIOM := js_of_eliom -w +A-4-7-9-37-38-39-41-42-44-45-48-70 -jsopt +base/runtime.js +JS_OF_OCAML := js_of_ocaml +ELIOMDEP := eliomdep +OCSIGENSERVER := ocsigenserver +OCSIGENSERVER.OPT := ocsigenserver.opt + +## Where to put intermediate object files. +## - ELIOM_{SERVER,CLIENT}_DIR must be distinct +## - ELIOM_CLIENT_DIR must not be the local dir. +## - ELIOM_SERVER_DIR could be ".", but you need to +## remove it from the "clean" rules... +export ELIOM_SERVER_DIR := _server +export ELIOM_CLIENT_DIR := _client +export ELIOM_TYPE_DIR := _server +DEPSDIR := _deps + +ifeq ($(DEBUG),yes) + GENERATE_DEBUG ?= -g + RUN_DEBUG ?= "-v" + DEBUG_JS ?= --noinline --disable=shortvar --pretty + # --debuginfo +endif + +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## General + +.PHONY: all css byte opt + +DIST_DIRS := $(ETCDIR) $(DATADIR) $(LIBDIR) $(LOGDIR) \ + $(FILESDIR)/avatars/tmp $(ELIOMSTATICDIR) \ + $(shell dirname $(CMDPIPE)) +JS_PREFIX := $(TEST_PREFIX)$(ELIOMSTATICDIR)/$(PROJECT_NAME) + +CONF_IN := $(wildcard *.conf.in) +CONFIG_FILES := $(patsubst %.conf.in,$(TEST_PREFIX)$(ETCDIR)/%.conf,$(CONF_IN)) +TEST_CONFIG_FILES := $(patsubst %.conf.in,$(TEST_PREFIX)$(ETCDIR)/%-test.conf,$(CONF_IN)) + + +all: css byte opt + +byte:: $(TEST_PREFIX)$(LIBDIR)/${PROJECT_NAME}.cma +opt:: $(TEST_PREFIX)$(LIBDIR)/${PROJECT_NAME}.cmxs + +byte opt:: ${JS_PREFIX}.js +byte opt:: $(CONFIG_FILES) + +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## The following part has been generated with os template. +## This will overload the default required binaries. + +## DO NOT MOVE IT ON TOP OF THE `all` RULE! + +include Makefile.$(PROJECT_NAME) + +## end of `include Makefile.$(PROJECT_NAME)` +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## Testing + +DIST_FILES = $(ELIOMSTATICDIR)/$(PROJECT_NAME).js $(LIBDIR)/$(PROJECT_NAME).cma + +.PHONY: test.byte test.opt staticfiles +test.byte: $(TEST_CONFIG_FILES) staticfiles $(addprefix $(TEST_PREFIX),$(DIST_DIRS) $(DIST_FILES)) css + @echo "==== The website is available at http://localhost:$(TEST_PORT) ====" + $(OCSIGENSERVER) $(RUN_DEBUG) -c $< +test.opt: $(TEST_CONFIG_FILES) staticfiles $(addprefix $(TEST_PREFIX),$(DIST_DIRS) $(patsubst %.cma,%.cmxs, $(DIST_FILES))) css + @echo "==== The website is available at http://localhost:$(TEST_PORT) ====" + $(OCSIGENSERVER.OPT) $(RUN_DEBUG) -c $< + +$(addprefix $(TEST_PREFIX), $(DIST_DIRS)): + mkdir -p $@ + +staticfiles: + cp -rf $(LOCAL_STATIC_CSS) $(LOCAL_STATIC_IMAGES) $(LOCAL_STATIC_FONTS) $(TEST_PREFIX)$(ELIOMSTATICDIR) + +##---------------------------------------------------------------------- +## Installing & Running + +.PHONY: install install.byte install.byte install.opt install.static install.etc install.lib install.lib.byte install.lib.opt run.byte run.opt +install: install.byte install.opt +install.byte: install.lib.byte install.etc install.static | $(addprefix $(PREFIX),$(DATADIR) $(LOGDIR) $(shell dirname $(CMDPIPE))) +install.opt: install.lib.opt install.etc install.static | $(addprefix $(PREFIX),$(DATADIR) $(LOGDIR) $(shell dirname $(CMDPIPE))) +install.lib: install.lib.byte install.lib.opt +install.lib.byte: $(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cma | $(PREFIX)$(LIBDIR) + install $< $(PREFIX)$(LIBDIR) +install.lib.opt: $(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cmxs | $(PREFIX)$(LIBDIR) + install $< $(PREFIX)$(LIBDIR) +install.static: $(TEST_PREFIX)$(ELIOMSTATICDIR)/$(PROJECT_NAME).js | $(PREFIX)$(STATICDIR) $(PREFIX)$(ELIOMSTATICDIR) + cp -r $(LOCAL_STATIC_CSS) $(PREFIX)$(FILESDIR) + cp -r $(LOCAL_STATIC_IMAGES) $(PREFIX)$(FILESDIR) + cp -r $(LOCAL_STATIC_FONTS) $(PREFIX)$(FILESDIR) + [ -z $(WWWUSER) ] || chown -R $(WWWUSER) $(PREFIX)$(FILESDIR) + install $(addprefix -o ,$(WWWUSER)) $< $(PREFIX)$(ELIOMSTATICDIR) +install.etc: $(TEST_PREFIX)$(ETCDIR)/$(PROJECT_NAME).conf | $(PREFIX)$(ETCDIR) + install $< $(PREFIX)$(ETCDIR)/$(PROJECT_NAME).conf + +.PHONY: +print-install-files: + @echo $(PREFIX)$(LIBDIR) + @echo $(PREFIX)$(ELIOMSTATICDIR) + @echo $(PREFIX)$(ETCDIR) + +$(addprefix $(PREFIX),$(ETCDIR) $(LIBDIR)): + install -d $@ +$(addprefix $(PREFIX),$(DATADIR) $(LOGDIR) $(ELIOMSTATICDIR) $(shell dirname $(CMDPIPE))): + install $(addprefix -o ,$(WWWUSER)) -d $@ + +run.byte: + @echo "==== The website is available at http://localhost:$(PORT) ====" + $(OCSIGENSERVER) $(RUN_DEBUG) -c ${PREFIX}${ETCDIR}/${PROJECT_NAME}.conf +run.opt: + @echo "==== The website is available at http://localhost:$(PORT) ====" + $(OCSIGENSERVER.OPT) $(RUN_DEBUG) -c ${PREFIX}${ETCDIR}/${PROJECT_NAME}.conf + +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## Aux + +# Use `eliomdep -sort' only in OCaml>4 +#ifeq ($(shell ocamlc -version|cut -c1),4) +#eliomdep=$(shell $(ELIOMDEP) $(1) -ppx -sort $(2) $(filter %.eliom %.ml,$(3)))) +#else +#eliomdep=$(3) +#endif +objs=$(patsubst %.ml,$(1)/%.$(2),$(patsubst %.eliom,$(1)/%.$(2),$(filter %.eliom %.ml,$(3)))) +#depsort=$(call objs,$(1),$(2),$(call eliomdep,$(3),$(4),$(5))) +depsort=$(shell ocaml tools/sort_deps.ml .depend $(patsubst %.ml,$(1)/%.$(2),$(patsubst %.eliom,$(1)/%.$(2),$(filter %.eliom %.ml,$(5))))) + +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## Config files + +ELIOM_MODULES=$(patsubst %,\,$(SERVER_ELIOM_PACKAGES)) +FINDLIB_PACKAGES=$(patsubst %,\,$(SERVER_PACKAGES)) +EDIT_WARNING=DON\'T EDIT THIS FILE! It is generated from $(PROJECT_NAME).conf.in, edit that one, or the variables in Makefile.options +SED_ARGS = -e "/^ *%%%/d" +SED_ARGS += -e "s|%%PROJECT_NAME%%|$(PROJECT_NAME)|g" +SED_ARGS += -e "s|%%DB_NAME%%|$(DB_NAME)|g" +SED_ARGS += -e "s|%%DB_HOST%%|$(DB_HOST)|g" +SED_ARGS += -e "s|%%DB_PORT%%|$(DB_PORT)|g" +SED_ARGS += -e "s|%%DB_USER%%|$(DB_USER)|g" +SED_ARGS += -e "s|%%DB_PASSWORD%%|$(DB_PASSWORD)|g" +SED_ARGS += -e "s|%%CMDPIPE%%|%%PREFIX%%$(CMDPIPE)|g" +SED_ARGS += -e "s|%%LOGDIR%%|%%PREFIX%%$(LOGDIR)|g" +SED_ARGS += -e "s|%%DATADIR%%|%%PREFIX%%$(DATADIR)|g" +SED_ARGS += -e "s|%%LIBDIR%%|%%PREFIX%%$(LIBDIR)|g" +SED_ARGS += -e "s|%%WARNING%%|$(EDIT_WARNING)|g" +SED_ARGS += -e "s|%%PACKAGES%%|$(FINDLIB_PACKAGES)|g" +SED_ARGS += -e "s|%%ELIOM_MODULES%%|$(ELIOM_MODULES)|g" +SED_ARGS += -e "s|%%FILESDIR%%|%%PREFIX%%$(FILESDIR)|g" +SED_ARGS += -e "s|%%ELIOMSTATICDIR%%|%%PREFIX%%$(ELIOMSTATICDIR)|g" +SED_ARGS += -e "s|%%APPNAME%%|$(shell basename `readlink $(JS_PREFIX).js` .js)|g" +SED_ARGS += -e "s|%%CSSNAME%%|$(shell readlink $(CSS_PREFIX).css)|g" +ifeq ($(DEBUG),yes) + SED_ARGS += -e "s|%%DEBUGMODE%%|\|g" +else + SED_ARGS += -e "s|%%DEBUGMODE%%||g" +endif + +LOCAL_SED_ARGS := -e "s|%%PORT%%|$(TEST_PORT)|g" +LOCAL_SED_ARGS += -e "s|%%USERGROUP%%||g" +GLOBAL_SED_ARGS := -e "s|%%PORT%%|$(PORT)|g" +ifeq ($(WWWUSER)$(WWWGROUP),) + GLOBAL_SED_ARGS += -e "s|%%USERGROUP%%||g" +else + GLOBAL_SED_ARGS += -e "s|%%USERGROUP%%|$(WWWUSER)$(WWWGROUP)|g" +endif + +ifneq ($(DO_NOT_RECOMPILE),yes) +JS_AND_CSS=$(JS_PREFIX).js $(CSS_PREFIX).css +endif + +$(CONFIG_FILES): $(TEST_PREFIX)$(ETCDIR)/%.conf: %.conf.in Makefile.options $(JS_AND_CSS) | $(TEST_PREFIX)$(ETCDIR) + sed $(SED_ARGS) $(GLOBAL_SED_ARGS) $< | sed -e "s|%%PREFIX%%|$(PREFIX)|g" > $@ + +$(TEST_CONFIG_FILES): $(TEST_PREFIX)$(ETCDIR)/%-test.conf: %.conf.in Makefile.options $(JS_AND_CSS) | $(TEST_PREFIX)$(ETCDIR) + sed $(SED_ARGS) $(LOCAL_SED_ARGS) $< | sed -e "s|%%PREFIX%%|$(TEST_PREFIX)|g" > $@ + +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## Server side compilation + +SERVER_INC_DEP := ${addprefix -package ,${SERVER_PACKAGES} ${SERVER_ELIOM_PACKAGES}} +SERVER_INC := ${addprefix -package ,${SERVER_PACKAGES} ${SERVER_ELIOM_PACKAGES}} +SERVER_DB_INC := ${addprefix -package ,${SERVER_PACKAGES} ${SERVER_DB_PACKAGES} ${SERVER_ELIOM_PACKAGES}} + +${ELIOM_TYPE_DIR}/%.type_mli: %.eliom + ${ELIOMC} -ppx -ppx ${I18N_PPX_REWRITER} -infer ${SERVER_INC} $< + +$(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cma: $(call objs,$(ELIOM_SERVER_DIR),cmo,$(SERVER_FILES)) | $(TEST_PREFIX)$(LIBDIR) + ${ELIOMC} -a -o $@ $(GENERATE_DEBUG) \ + $(call depsort,$(ELIOM_SERVER_DIR),cmo,-server,$(SERVER_DB_INC),$(SERVER_FILES)) + +$(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cmxa: $(call objs,$(ELIOM_SERVER_DIR),cmx,$(SERVER_FILES)) | $(TEST_PREFIX)$(LIBDIR) + ${ELIOMOPT} -a -o $@ $(GENERATE_DEBUG) \ + $(call depsort,$(ELIOM_SERVER_DIR),cmx,-server,$(SERVER_DB_INC),$(SERVER_FILES)) + +%.cmxs: %.cmxa + $(ELIOMOPT) -shared -linkall -o $@ $(GENERATE_DEBUG) $< + +${ELIOM_SERVER_DIR}/%_db.cmi: %_db.mli + ${ELIOMC} -c -ppx ${SERVER_DB_INC} $(GENERATE_DEBUG) $< +${ELIOM_SERVER_DIR}/%.cmi: %.mli + ${ELIOMC} -c ${SERVER_INC} $(GENERATE_DEBUG) $< + +${ELIOM_SERVER_DIR}/%.cmi: %.eliomi + ${ELIOMC} -ppx -ppx ${I18N_PPX_REWRITER} -c ${SERVER_INC} $(GENERATE_DEBUG) $< + +${ELIOM_SERVER_DIR}/%_db.cmo: %_db.ml + ${ELIOMC} -c -ppx ${SERVER_DB_INC} $(GENERATE_DEBUG) $< +${ELIOM_SERVER_DIR}/%.cmo: %.ml + ${ELIOMC} -c ${SERVER_INC} $(GENERATE_DEBUG) $< +${ELIOM_SERVER_DIR}/%.cmo: %.eliom + ${ELIOMC} -ppx -ppx ${I18N_PPX_REWRITER} -c ${SERVER_INC} $(GENERATE_DEBUG) $< + +${ELIOM_SERVER_DIR}/%_db.cmx: %_db.ml + ${ELIOMOPT} -c -ppx ${SERVER_DB_INC} $(GENERATE_DEBUG) $< +${ELIOM_SERVER_DIR}/%.cmx: %.ml + ${ELIOMOPT} -c ${SERVER_INC} $(GENERATE_DEBUG) $< +${ELIOM_SERVER_DIR}/%.cmx: %.eliom + ${ELIOMOPT} -ppx -ppx ${I18N_PPX_REWRITER} -c ${SERVER_INC} $(GENERATE_DEBUG) $< + +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## Client side compilation + +CLIENT_LIBS := ${addprefix -package ,${CLIENT_PACKAGES}} +CLIENT_INC := ${addprefix -package ,${CLIENT_PACKAGES}} + +CLIENT_OBJS := $(filter %.eliom %.ml, $(CLIENT_FILES)) +CLIENT_OBJS := $(patsubst %.eliom,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_OBJS}) +CLIENT_OBJS := $(patsubst %.ml,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_OBJS}) + +$(ELIOM_CLIENT_DIR)/os_prologue.js: \ + $(shell ocamlfind query -r -predicates byte -a-format $(CLIENT_PACKAGES)) + ${JS_OF_ELIOM} -jsopt --dynlink -o $@ $(GENERATE_DEBUG) $(CLIENT_INC) \ + ${addprefix -jsopt ,$(DEBUG_JS)} + +ifeq ($(DEBUG),yes) +$(JS_PREFIX).js: $(call objs,$(ELIOM_CLIENT_DIR),js,$(CLIENT_FILES)) | $(TEST_PREFIX)$(ELIOMSTATICDIR) $(ELIOM_CLIENT_DIR)/os_prologue.js + cat $(ELIOM_CLIENT_DIR)/os_prologue.js $(call depsort,$(ELIOM_CLIENT_DIR),js,-client,$(CLIENT_INC),$(CLIENT_FILES)) > $(JS_PREFIX)_tmp.js && \ + HASH=`md5sum $(JS_PREFIX)_tmp.js | cut -d ' ' -f 1` && \ + mv $(JS_PREFIX)_tmp.js $(JS_PREFIX)_$$HASH.js && \ + ln -sf $(PROJECT_NAME)_$$HASH.js $@ +else +$(JS_PREFIX).js: $(call objs,$(ELIOM_CLIENT_DIR),cmo,$(CLIENT_FILES)) | $(TEST_PREFIX)$(ELIOMSTATICDIR) + ${JS_OF_ELIOM} -ppx -o $(JS_PREFIX)_tmp.js $(GENERATE_DEBUG) $(CLIENT_INC) ${addprefix -jsopt ,$(DEBUG_JS)} \ + $(call depsort,$(ELIOM_CLIENT_DIR),cmo,-client,$(CLIENT_INC),$(CLIENT_FILES)) + HASH=`md5sum $(JS_PREFIX)_tmp.js | cut -d ' ' -f 1` && \ + mv $(JS_PREFIX)_tmp.js $(JS_PREFIX)_$$HASH.js && \ + ln -sf $(PROJECT_NAME)_$$HASH.js $@ +endif + +${ELIOM_CLIENT_DIR}/%.cmi: %.mli + ${JS_OF_ELIOM} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< + +${ELIOM_CLIENT_DIR}/%.cmo: %.eliom + ${JS_OF_ELIOM} -ppx -ppx ${I18N_PPX_REWRITER} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< + +${ELIOM_CLIENT_DIR}/%.cmo: %.ml + ${JS_OF_ELIOM} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< + +${ELIOM_CLIENT_DIR}/%.cmi: %.eliomi + ${JS_OF_ELIOM} -ppx -ppx ${I18N_PPX_REWRITER} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< + +${ELIOM_CLIENT_DIR}/%.js: ${ELIOM_CLIENT_DIR}/%.cmo + ${JS_OF_OCAML} $(DEBUG_JS) +base/runtime.js $< + +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## Dependencies + +# DO NOT include `.depend' for the following commands: db-*, clean, distclean +is_db_command=$(shell echo $(1) | grep -q "db-" && echo "true" || echo "false") +ifneq ($(call is_db_command,$(MAKECMDGOALS)),true) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(MAKECMDGOALS),i18n-update) +ifneq ($(MAKECMDGOALS),distclean) +include .depend +endif +endif +endif +endif + +.depend: $(patsubst %,$(DEPSDIR)/%.server,$(SERVER_FILES)) $(patsubst %,$(DEPSDIR)/%.client,$(CLIENT_FILES)) + @cat $^ > $@ + +$(DEPSDIR)/%.ml.server: %.ml | $(DEPSDIR) $(SERVER_FILES) + $(ELIOMDEP) -server -ppx $(SERVER_DB_INC) $< > $@.tmp && mv $@.tmp $@ + +$(DEPSDIR)/%.mli.server: %.mli | $(DEPSDIR) $(SERVER_FILES) + $(ELIOMDEP) -server -ppx $(SERVER_DB_INC) $< > $@.tmp && mv $@.tmp $@ + +$(DEPSDIR)/%.eliom.server: %.eliom | $(DEPSDIR) $(SERVER_FILES) + $(ELIOMDEP) -server -ppx -ppx ${I18N_PPX_REWRITER} $(SERVER_INC_DEP) $< > $@.tmp && mv $@.tmp $@ + +$(DEPSDIR)/%.eliomi.server: %.eliomi | $(DEPSDIR) $(SERVER_FILES) + $(ELIOMDEP) -server -ppx -ppx ${I18N_PPX_REWRITER} $(SERVER_INC_DEP) $< > $@.tmp && mv $@.tmp $@ + +$(DEPSDIR)/%.ml.client: %.ml | $(DEPSDIR) + $(ELIOMDEP) -client $(CLIENT_INC) $< > $@.tmp && mv $@.tmp $@ + +$(DEPSDIR)/%.eliom.client: %.eliom | $(DEPSDIR) + $(ELIOMDEP) -client -ppx -ppx ${I18N_PPX_REWRITER} $(CLIENT_INC) $< > $@.tmp && mv $@.tmp $@ + +$(DEPSDIR)/%.eliomi.client: %.eliomi | $(DEPSDIR) + $(ELIOMDEP) -client -ppx -ppx ${I18N_PPX_REWRITER} $(CLIENT_INC) $< > $@.tmp && mv $@.tmp $@ + +$(DEPSDIR): + mkdir $@ + +##---------------------------------------------------------------------- + +##---------------------------------------------------------------------- +## Clean up + +clean:: clean-style mobile-clean i18n-clean + -rm -f *.cm[ioax] *.cmxa *.cmxs *.o *.a *.annot + -rm -f *.type_mli + -rm -rf ${ELIOM_CLIENT_DIR} ${ELIOM_SERVER_DIR} + +distclean: clean + -rm -rf $(TEST_PREFIX) $(DEPSDIR) .depend diff --git a/daegsrv/Makefile.style b/daegsrv/Makefile.style new file mode 100644 index 0000000..227124f --- /dev/null +++ b/daegsrv/Makefile.style @@ -0,0 +1,119 @@ +# The following condition is used to determinate if the sed version is BSD or +# GNU. It is useful because `sed -i` has a different implementation (and this +# command is needed by $(CSS_DEST). It uses the fact that `sed --version` +# doesn't exist for the BSD version. + +.PHONY: css check_sass clean-style + +check_sed: +ifneq ($(shell sed --version 2> /dev/null),) +SED = sed -i +else +SED = sed -i .css +endif + +css: $(CSS_DEST) + +define ERROR_SASS + +Error: SASS not found. + +Ocsigen Start gives the choice to use SASS (a CSS preprocessor) to write +stylesheets. We encourage you to use SASS and the template needs it by default. +See https://sass-lang.com for more information. +If you don't really want to use it, you can change the value of the variable +USE_SASS to "no" in Makefile.options to use CSS. + +endef + + +check_sass: +ifeq ($(strip $(USE_SASS)),yes) +ifeq ($(shell which sassc),) +ifeq ($(shell which sass),) +$(error $(ERROR_SASS)) +endif +endif +endif + +ifeq ($(USE_NPM), yes) +PACKAGE_JSON = package.json +NPM_POSTCSS = node_modules/postcss-cli +NPM_AUTOPREFIXER = node_modules/autoprefixer +POSTCSS = node_modules/.bin/postcss + +$(NPM_POSTCSS): $(PACKAGE_JSON) + npm install postcss-cli@7.1.2 + +$(NPM_AUTOPREFIXER): $(PACKAGE_JSON) + npm install autoprefixer@9.8.6 + +$(PACKAGE_JSON): + npm init --yes +else +PACKAGE_JSON = +NPM_POSTCSS = +NPM_AUTOPREFIXER = +POSTCSS = postcss +endif + +##---------------------------------------------------------------------- +## SASS rules + +# If $(USE_SASS) (see Makefile.options) is set to yes, it will compile and +# compress all SASS files and save it in $(LOCAL_CSS). +# If SASS is not activated, it will concatenate all CSS files (listed in +# $(CSS_FILES)) in $(LOCAL_CSS). +# In both cases, external CSS files ($(EXTERNAL_CSS_FILES)) are copied. +$(LOCAL_CSS): $(PACKAGE_JSON) $(NPM_POSTCSS) $(NPM_AUTOPREFIXER) $(LOCAL_STATIC_CSS)/.import-external-css | check_sass +ifeq "$(USE_SASS)" "yes" +ifeq ($(shell which sassc),) + [ -d $(SASSDIR) ] && \ + SASS_PATH=$(SASS_PATH) sass --style compressed $(SASS_SRC) $@ +else + [ -d $(SASSDIR) ] && \ + sassc -t compressed $(addprefix -I ,$(subst :, ,$(SASS_PATH))) $(SASS_SRC) $@ +endif + $(POSTCSS) --use autoprefixer --replace $@ +else + cat $(CSS_FILES) > $@ +endif + +##---------------------------------------------------------------------- +## CSS rules + +$(CSSDIR): + mkdir -p $@ + +# Copy the CSS file $(LOCAL_CSS) in $(CSS_DEST) after adding a hash in the name +# and make a symlink for $(PROJECT_NAME).css which is used in index.html. +# FIXME: md5sum is not by default on Mac OSX: it must be installed with brew. +# Instead of md5sum, md5 is present but the output is different. +$(CSS_DEST): $(LOCAL_CSS) | $(CSSDIR) check_sed + HASH=`cat $< | md5sum | cut -d ' ' -f 1` && \ + cp $< $(CSS_PREFIX)_$$HASH.css && \ + $(SED) '1s/^/@charset "UTF-8";/' $(CSS_PREFIX)_$$HASH.css && \ + ln -sf $(PROJECT_NAME)_$$HASH.css $@ +# Charset is necessary for iOS. +# Including it in scss does not work because sass removes it. + +##---------------------------------------------------------------------- +## External CSS + +# Copy files from other projects into the working directory. +# By default, it imports all CSS files from ocsigen-toolkit because the template +# needs it. +# See EXTERNAL_CSS_FILES definition in Makefile.options for more information. +# It is executed with every run of make to be sure external CSS files are +# up-to-date and it allows to add other external CSS files between two +# compilation processes. + +$(LOCAL_STATIC_CSS)/.import-external-css: $(EXTERNAL_CSS_FILES) +ifneq "$(EXTERNAL_CSS_FILES)" "" + cp $(EXTERNAL_CSS_FILES) $(LOCAL_STATIC_CSS) +endif + touch $(LOCAL_STATIC_CSS)/.import-external-css + +clean-style: + $(RM) $(LOCAL_CSS) $(LOCAL_STATIC_CSS)/$(PROJECT_NAME).css.map + $(RM) -r .sass-cache diff --git a/daegsrv/README.md b/daegsrv/README.md new file mode 100644 index 0000000..0a64e6a --- /dev/null +++ b/daegsrv/README.md @@ -0,0 +1,351 @@ +Instructions +============ + +This project is (initially) generated by `eliom-distillery` as the basic +project `daegsrv`. + +Note that external dependencies are required prior to building the +project. Postgres is mandatory. By default, NPM is used for +automatically installing various NPM packages; you can disable this +via the `USE_NPM` variable in `Makefile.options` if you prefer to use +a system-wide NPM installation. SASS is optional, but not installing +it may negatively impact the rendering of the pages generated. All +needed packages (Postgres, NPM, SASS, ...) and required OPAM packages can be +installed via the command (from the daegsrv directory): + +```shell +opam install . +``` + +If you have issues with the NPM provided by your distribution, you can +use [NVM](https://github.com/creationix/nvm). If NPM is too old (< 2.0), +you can try updating it with `sudo npm install -g npm`. Depending on your +setup, you may have to update your `$PATH` for the new `npm` to become +visible. + +Generally, you can compile it and run ocsigenserver on it by + +```shell +make db-init +make db-create +make db-schema +make test.byte (or test.opt) +``` + +Then connect to `http://localhost:8080` to see the running app skeleton. +Registration will work only if sendmail if configured on your system. +But the default template will print the activation link on the standard +output to make it possible for you to create your first users (remove this!). + +See below for other useful targets for make. + +Generated files +--------------- + +The following files in this directory have been generated by +`eliom-distillery`: + +- `daegsrv*.eliom[i]` + Initial source file of the project. + All Eliom files (*.eliom, *.eliomi) in this directory are + automatically compiled and included in the application. + To add a .ml/.mli file to your project, + append it to the variable `SERVER_FILES` or `CLIENT_FILES` in + Makefile.options. + +- `static/`. + This folder contains the static data for your app. + The content will be copied into the static file directory + of the server and included in the mobile app. + Put your CSS or additional JavaScript files here. + +- `Makefile.options` + Configure your project here. + +- `daegsrv.conf.in`. + This file is a template for the configuration file for + Ocsigen Server. You will rarely have to edit it yourself - it takes its + variables from the Makefile.options. This way, the installation + rules and the configuration files are synchronized with respect to + the different folders. + +- `mobile` + The files needed by Cordova mobile apps + +- `Makefile` + This contains all rules necessary to build, test, and run your + Eliom application. See below for the relevant targets. + +- `README.md` + + +Makefile targets +---------------- + +Here's some help on how to work with this basic distillery project: + +- Initialize, start, create, stop, delete a local db, or show status: +```Shell +make db-init +make db-start +make db-create +make db-stop +make db-delete +make db-status +``` + +- Test your application by compiling it and running ocsigenserver locally +``` +make test.byte (or test.opt) +``` + +- Compile it only +```Shell +make all (or byte or opt) +``` + +- Deploy your project on your system +```Shell +sudo make install (or install.byte or install.opt) +``` + +- Run the server on the deployed project +```Shell +sudo make run.byte (or run.opt) +``` + +If `WWWUSER` in the `Makefile.options` is you, you don't need the +`sudo`. If Eliom isn't installed globally, however, you need to +re-export some environment variables to make this work: +```Shell +sudo PATH=$PATH OCAMLPATH=$OCAMLPATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH make run.byte/run.opt +``` + +- If you need a findlib package in your project, add it to the + variables `SERVER_PACKAGES` and/or `CLIENT_PACKAGES`. The configuration + file will be automatically updated. + +Build the mobile applications +----------------------------- + +## Prepare the mobile infrastructure. + +### For all mobile platforms: + +Make sure you have a working NPM installation. The needed NPM packages +(like Cordova) will be installed automatically. + +Warning: NPM packages (and especially Cordova plugins) are very sensitive to +version changes. You may have to change version numbers in +`mobile/config.xml.in` if something goes wrong during app generation. +You may also have problems with old versions of `gradle` or wrong versions +of Android packages ... + +If npm is causing a lot of errors (on Debian) in the following parts of the installation, an advice would be to uninstall nodejs and npm and do a clean installation of them **with aptitude**. + +This installation was tested with those versions: + +``` +npm : 6.14.12 +nodejs : v10.24.1 +``` + +**Be prepared! You're entering an unstable world!** + +### For Android: + +- Install JDK 11 (`openjdk-11-jdk` package in Debian/Ubuntu) + + Run those commands and look carefully if the checked option for java and javac are from the same repository: + ``` + sudo update-alternatives --config java + sudo update-alternatives --config javac + ``` +- Install Gradle (`gradle` package in Debian/Ubuntu) +- Download and untar the [Android SDK](http://developer.android.com) (the smaller version without Android Studio is sufficent), rename it so that you have a `$HOME/android-sdk-linux/tools` folder. +- Using the Android package management interface (or sdkmanager): + * List All System Images Available for Download: `sdkmanager --list | grep system-images`\ + (*As an example we're going to choose "system-images;android-26;default;x86" but you can choose your way.*) + * Download Image: sdkmanager --install "system-images;android-26;default;x86"\ + (*Be aware that version > android-26 may not work.*) + +If you want to emulate an Android device, you need to create an emulator : + +``` +echo "no" | avdmanager --verbose create avd --force --name "generic_10" --package "system-images;android-26 default;x86" --tag "default" --abi "x86" +# Check every available options that offers avdmanager to customize your emulator as you wish. +``` + +There is a couple more steps to follow: + +Unfortunately there are two named emulator binary file, which are located under `$ANDROID_SDK/tools/emulator` and the other is under `$ANDROID_SDK/emulator/`.\ +Make sure you have the right emulator configure (you need to add `$ANDROID_SDK/emulator` to your env PATH). + +In order to do this: + +1. Add in your `~/.bashrc` (or `~/.zshrc`) file: + ```sh + export ANDROID_SDK=$HOME'your_path_to_android_sdk' + export PATH=$ANDROID_SDK/emulator:$PATH + export PATH=$ANDROID_SDK/tools:$PATH + export PATH=$ANDROID_SDK/tools/bin:$PATH + export PATH=$ANDROID_SDK/platform-tools:$PATH + export ANDROID_SDK_ROOT=$ANDROID_SDK + export ANDROID_AVD_HOME=$HOME/.android/and + alias emulator='$ANDROID_SDK/emulator/emulator' + ``` +2. Then execute this command in your shell: `source ~/.bash_profile` +3. And show the installed emulators with: `emulator -list-avds`\ + You should have something displaying like: + ```sh + generic_10 + # Or even something like : + Pixel_2_API_29 + Pixel_3a_API_29 + Pixel_C_API_29 + ``` + +### For iOS: + +- Xcode installs all dependencies you need. + +- Some iOS-specific code exists. You should check it out. For instance, looking carefully at the [`PROJECT_NAME.conf.in`](PROJECT_NAME.conf.in) file is mandatory if you're building an iOS app. + +### For Windows: + +Ocsigen Start uses +[cordova-hot-code-push-plugin](https://github.com/nordnet/cordova-hot-code-push) +to upload local files (like CSS and JavaScript files, images and logo) when the +server code changes. + +Unfortunately, this plugin is not yet available for Windows Phone. However, as +ocsigen Start also builds the website part, an idea is to run the website into a +WebView on Windows Phones. + +Even if Cordova allows you to build Windows app, it doesn't authorize you to +load an external URL without interaction with the user. + +Another solution is to build an [Hosted Web +App](https://developer.microsoft.com/en-us/windows/bridges/hosted-web-apps). It +makes it possible to create easily an application based on your website. You can +also use Windows JavaScript API (no OCaml binding available for the moment) to +get access to native components. You can create the APPX package (package format +for Windows app) by using [Manifold JS](http://manifoldjs.com/), even if you are on MacOS X or Linux. + +If you are on Windows, you can +use [Visual Studio Community](https://www.visualstudio.com/fr/vs/community/). +The Visual Studio Community solution is recommended to test and debug. You can +see all errors in the JavaScript console provided in Visual Studio. + +[Here](https://blogs.windows.com/buildingapps/2016/02/17/building-a-great-hosted-web-app/#3mlzw0giKcuGZDeq.97) a +complete tutorial from the Windows blog for both versions (with Manifold JS and +Visual Studio). + +If you use the Manifold JS solution, you need to sign the APPX before installing it on a device. + +## Launching the mobile app + +The following examples are described for Android but they are also available +for iOS: you only need to replace `android` by `ios`. + +- Launch an Ocsigen server serving your app: +``` +make test.opt +``` + +In the following commands, if `APP_REMOTE` is `yes`, the mobile app will +be created by getting all the necessary files (js, etc) from a server. +This may be used to create a mobile app for an which has not been +compiled locally. With `APP_REMOTE=no`, the local files will be used. + +The remote server address is given in the variable `APP_SERVER`. +Replace `${YOUR_SERVER}` by `${YOUR_IP_ADDRESS}:8080` in the following +commands if you want to test on your local machine. + +- To run the application in the emulator, use: + +``` +make APP_SERVER=http://${YOUR_SERVER} APP_REMOTE=no APP=dev emulate-android +``` + +The above command will attempt to launch your app in the Android emulator that +you have configured previously. Depending on your setup, you may need to start +the emulator before running the command. + +Note: If the emulator does not start on your Linux system because of +a library problem, you can try to set the environment variable +`ANDROID_EMULATOR_USE_SYSTEM_LIBS` to `1` to make it start (see +https://developer.android.com/studio/command-line/variables.html for +details). + +To run the application on a connected device, use: + +``` +make APP_SERVER=http://${YOUR_SERVER} APP_REMOTE=no APP=dev run-android +``` +Notice that the `APP_SERVER` argument needs to point to your LAN or public +address (e.g., `192.168.1.x`), not to `127.0.0.1` (neither to `localhost`). The +reason is that the address will be used by the Android emulator/device, inside +which `127.0.0.1` has different meaning; it points to the Android host itself. + +If you only want to build the mobile application, you can use: +``` +make APP_SERVER=http://${YOUR_SERVER} APP_REMOTE=no APP=dev android +``` + +Before uploading on Google Play Store, check the variables in Makefile.options +(MOBILE_APP_IP, version number, etc). +You'll need to build a release version (default is debug version): +``` +make APP_SERVER=http://${YOUR_SERVER} APP_REMOTE=no android-release +``` +then sign it (see Android documentation). + +If you want the application URL to include a path +(`http://${YOUR_SERVER}${PATH}`), +you need to provide an additional `APP_PATH` argument, e.g., +`APP_PATH=/foo`. You need to include the leading `/`, but no trailing +`/`. You also need to modify the `daegsrv.conf.in` with a +[`` tag](http://ocsigen.org/ocsigenserver/manual/config#h5o-31). + +Note: if any of the mobile-related targets fails due to the inexistent +`node` command, you may need to create a symlink from `node` to +`nodejs`, e.g., as follows: + +``` +ln -s /usr/bin/nodejs /usr/local/bin/node +``` + +## Update the mobile application. + +The mobile app is updated automatically at launch time, every time the +server has been updated. To do that, Ocsigen Start is using Cordova Hot +Code Push. + +In order to make it work, you MUST use the following command every time +you update the server: +``` +make APP_SERVER=http://${YOUR_SERVER} APP_REMOTE={yes|no} chcp +``` + +## Use Makefile.local file. + +You need to define `APP_REMOTE` and `APP_SERVER` each time you want to build +the mobile application or to update it. The `APP` variable is not mandatory per +say but when set to `dev` it enables cleartext traffic, so you might want to +keep it on while working on dev builds. + +If you don't want to pass the variables `App`, `APP_SERVER` and +`APP_REMOTE` every time, you can change the values of these variables in +`Makefile.local.example` and rename this file to `Makefile.local`. This way, +the variables `App`, `APP_REMOTE` and `APP_SERVER` are not mandatory when you build +or update the mobile application. You can use: +``` +make chcp +make run-android +make run-ios +... +``` + +This file is meant for rules and variables that are only relevant for local development +and it must not be deployed or shared (by default, this file is ignored by Git). diff --git a/daegsrv/assets/daegsrv_Demo_i18n.tsv b/daegsrv/assets/daegsrv_Demo_i18n.tsv new file mode 100644 index 0000000..f11d4d8 --- /dev/null +++ b/daegsrv/assets/daegsrv_Demo_i18n.tsv @@ -0,0 +1,121 @@ +allows_get_information_currently_connected_user provides information about the currently connected user (server or client side). vous autorise à obtenir les information de l'utilisateur courant connecté (côté serveur ou côté client). +always_get_current_user_using_module Always get the current user id using module Récupérez toujours l'ID de l'utilisateur courant en utilisant le module +cache Cache Cache +cache_1 Caching the data Mise en cache des données +cache_2 Module {{eliom_cscache}} implements a cache of data that is designed for Eliom's client-server programming model. It permits saving a client-side copy of the data. Have a look at the module {{os_user_proxy}} to see how it works (and use this module for getting information about Ocsigen Start's users). Le module {{eliom_cscache}} implémente un cache de données construit pour le modèle client-serveur d'Eliom. Il permet de sauvegarder une copie des données du client. Jetez un oeil au module {{os_user_proxy}} pour comprendre son fonctionnement (et utilisez ce module pour obtenir des informations sur les utilisateurs d'Ocsigen Start). +cache_3 When you get a piece of data through {{eliom_cscache}} from client-side, the request to the server is done only if the data is not already in the client-side cache. On server-side, {{eliom_cscache}} is using a temporary cache (with \"request\" scope) to avoid fetching the data several times from the database during the same request. This server-side cache is automatically sent to the client to fill the client-side cache. If you want to avoid too many requests from the client, prefill the server-side cache with the data the client program will need. Quand une donnée du client est obtenue via {{eliom_cscache}}, la requête vers le serveur est faite uniquement si la donnée ne se trouve pas déjà dans le cache côté client. Du côté serveur, {{eliom_cscache}} utilise un cache temporaire (avec une portée de type "request") afin d'éviter de récupérer les données de la base de données plusieurs fois sur une même requête. Ce cache côté serveur est automatiquement envoyé au client pour remplir son cache. Pour éviter un trop grand nombre de requêtes côté client, pré-remplissez le cache côté serveur avec les données dont le programme côté client a besoin. +cache_4 In the near future, {{eliom_cscache}} will enable saving persistent data, which is useful for implementing off-line applications. Prochainement, {{eliom_cscache}} permettra de sauvegarder des données de manière persistante, ce qui peut être utile pour implémenter des applications hors ligne. +calendar Calendar Calendrier +carousel_1 Carousel Carousel +carousel_2 Carousel: page with tabs Carousel : page avec onglets +carousel_third_example_1 Example of a vertical circular carousel (wheel). Try with a touch screen. Exemple de carousel vertical circulaire (wheel). Essayez avec un écran tactile. +carousel_wheel Wheel carousel Carousel avec roulette +eliom_ref Eliom references + OS dates Références Eliom + dates OS +eliom_ref_1 We use an Eliom reference to record the last time you visited this page. Eliom references make it possible to save, server-side, data specific to one user, one browser, or one tab. Nous utilisons les références Eliom pour sauvegarder la date de la dernière fois que vous avez visité cette page. Les références Eliom permettent de sauvegarder côté serveur des données propres à un utilisateur, à un navigateur ou à un onglet. +eliom_ref_2 The value is different for each user. La valeur est différente pour chaque utilisateur. +eliom_ref_3 The reference has been updated. Come back later! La référence a été mise à jour. Revenez plus tard ! +eliom_ref_first_visit This is your first visit. Ceci est votre première visite. +eliom_ref_last_visit The last time you visited was: La dernière fois que vous avez visité ce lien était : +example_tip This is an example of tip. Ceci est un exemple de tip. +exchange_msg_between_users Module {{os_notif}} enables sending information to client applications (notifications, new messages ...). Le module {{os_notif}} permet d'envoyer des données aux applications client (notifications, nouveaux messages,...). +external_service external service service externe +fill_input_form_send_message Fill in the input form to send a message to all other tabs. Remplissez le formulaire pour envoyer un message à tous vos autres onglets ouverts. +friday Friday Vendredi +general_principles General principles Principes généraux +internal_link internal link lien interne +internationalization {{{capitalize?I||i}}}nternationalization {{{capitalize?I||i}}}nternationalisation +internationalization_1 Ocsigen Start uses Ocsigen-i18n for internationalizing your app. Ocsigen-i18n defines a PPX syntax extension for automatically selecting language-dependent text for each user. The user can choose his preferred language from the settings page. By default the browser's language is used. Ocsigen Start utilise Ocsigen-i18n for internationaliser les applications. Ocsigen-i18n définit une extension de syntaxe PPX qui sélectionne automatiquement les textes en fonction de la langue de l'utilisateur courant. L'utilisateur peut choisir sa langue préférée dans la page de paramètres. Par défaut, la langue du navigateur est utilisée. +internationalization_2 Write your translations (as tab-separated-values) in file {{f1}}. File {{f2}} is generated automatically from this file. Écrivez vos traductions (au format "tab-separated-values") dans le fichier {{f1}}. Le fichier {{f2}} est généré automatiquement à partir de ce fichier. +internationalization_3 Have a look at the OCaml code of this page to discover some features of the module Ocsigen-i18n. Jetez un coup d'œil au code OCaml de cette page pour découvrir quelques astuces du module Ocsigen-i18n. +internationalization_4 To make your life easier, the entries used for this whole demo sub-site have been split into their own file {{f}}. When you feel ready to make this project your own, you can freely delete all the {{demo_prefix}} files, and {{f}}. Pour rendre votre vie un peu plus facile, les entrées utilisées dans tout ce sous-site démo ont été séparées dans leur propre fichier {{f}}. Quand vous vous sentez prêt à faire de ce projet le votre, vous pourrez librement supprimer tous les fichiers {{demo_prefix}}, ainsi que {{f}}. +intro Demo: introduction Demo : introduction +intro_1 Ocsigen provides a set of largely independent tools for implementing Web and mobile applications (OCaml to JS compiler, Web server, typed HTML, etc.). Ocsigen can be used to implement, depending on your needs, either traditional Web sites (server-side), or client-side apps running in a browser, or full client-server apps, running both in a browser and as mobile apps. Ocsigen Start is a template for quickly writing such a client-server app. Ocsigen fournit un ensemble d'outils largement indépendants pour programmer des applications Web et mobiles (compilateur OCaml vers Javascript, serveur Web, HTML typé, etc.). Cela vous permet d'écrire, selon vos besoins, des sites Web traditionnels (côté serveur), des applications clientes s'exécutant dans une page Web, ou de véritables applications client-serveur, pouvant s'exécuter dans un navigateur ou comme application mobile. Ocsigen Start est un template prêt à utiliser pour ce type d'applications client-serveur. +intro_2 Ocsigen Eliom is a set of libraries for Web programming in OCaml: sessions, services, client-server communication, etc. It also contains an extension of the OCaml language to write a client-server program as a single app. Code annotations permit distinguishing between the code to be included in the server app, the code for the client app, and the code to be included in both of them. Have a look at the code of this app to learn how to generate typed HTML pages, how to call server function from client side, or how to send information to client applications (notifications). Ocsigen Eliom est un ensemble de bibliothèques pour la programmation Web en OCaml : sessions, services, communication client-serveur, etc. Il contient aussi une extension du langage OCaml permettant d'écrire des applications client-serveur. Des annotations du code permettent de distinguer le code devant être inclus dans l'application serveur, du code qui doit être inclus dans l'application cliente. Regardez le code source de cette application pour apprendre comment générer des pages HTML bien typées, comment appeler une fonction serveur depuis un programme client, ou encore comment envoyer des informations aux clients connectés (notifications). +intro_3 Read tutorials on Ocsigen's Web site for a more detailed introduction. Lisez les tutoriels du site d'Ocsigen pour une introduction plus détaillée. +links_and_forms Links and forms Liens et formulaires +links_and_forms_1 Here is an example of an {{t1}}, and an example of link towards an {{t2}}. Voici un exemple de {{t1}}, et un exemple de lien vers un {{t2}}. +links_and_static_files Links, services and static files Liens, services et fichiers statiques +log_in_to_see_demo Log in to see the demo. Connectez-vous pour voir la démonstration. +look_module_tip Look at the code to see how it is defined. Regardez le code pour voir comment c'est défini. +monday Monday Lundi +never_trust_client_pending_user_id Never trust a client sending its own user id! Ne faites jamais confiance à un client envoyant son propre ID d'utilisateur ! +no_user_create_accounts No user. Create some accounts to test. Aucun utilisateur. Créez quelques comptes pour tester. +notification Notifications Notifications +notification_got got Reçu +open_multiple_tabs_browsers Open this page in multiple tabs or browsers. Ouvrez cette page dans plusieurs onglets et fenêtres. +ot_carousel_first_example_1 This is a first example of Ocsigen Toolkit's carousel. Voici le premier exemple du carousel d'Ocsigen Toolkit. +ot_carousel_first_example_2 The carousel displays a number of blocks side-by-side (or vertically stacked). Le carousel sert à afficher des blocs côte-à-côte (ou empilés verticalement). +ot_carousel_first_example_3 To switch to a different block, use the buttons in the carousel. Pour vous rendre sur un autre bloc, utilisez les boutons dans le carousel. +ot_carousel_first_example_4 On touch screens you can also swipe with your fingers. Sur les écrans tactiles, swipez avec les doigts. +ot_carousel_second_example_1 This page demonstrates how to use Ocsigen Toolkit's carousel to display a page with several tabs. Cette page montre comment utiliser le carousel d'Ocsigen Toolkit pour afficher une page avec des onglets. +ot_carousel_second_example_2 Try to swipe on a touch screen. Sur écran tactile, glissez pour changer d'onglet. +ot_carousel_second_example_3 Try on a small screen or browser window to see how the tabs stick on top while scrolling the page. Essayez sur un petit écran pour voir comment la barre d'onglets se fixe en haut de la page quand vous faites défiler la page vers le bas. +ot_tongue_1 This is an example of page with a tongue coming from the bottom of the screen. try to slide it with your finger on a mobile screen. Ceci est un exemple de page avec une languette partant du bas de l'écran. Essayez de la faire glisser vers le haut avec le doigt sur un téléphone mobile. +pagetransition Page transitions Transition de pages +pagetransition_add_button Add Ajouter +pagetransition_back_button Go back Retourner +pagetransition_detail_page Detail Page Page de Détails +pagetransition_intro This demo illustrates smooth page transitions and the retention of a page's scroll position. To see the effects scroll a bit and click on one of the links. When you return to this page by hitting the back button the DOM of the page along with its scroll position will be restored from the cache without being charged from the server or generated on the client. Cette démo présente des changement de page animés et la mémorisation des positions de scroll. Pour voir ces effets faites défiler la page un peu vers le bas et cliquez sur un des liens de la liste. Quand vous retournerez sur cette page en appuyant sur le bouton «retour», le DOM de la page sera servi directement du cache sans être généré une nouvelle fois. La position du défilement aura été sauvegardé. +pagetransition_list_page List Page Page Liste +pgocaml Database request Requête à la base de données. +pgocaml_description_1 This page shows signed-up users fetched from the database. Cette page montre tous les utilisateurs inscrits qui ont été récupérés de la base de données. +pgocaml_description_2 Have a look at the source code to see how to make a DB request with PGOCaml. Regardez dans le code source comment réaliser une requête à la base de données en utilisant PGOcaml. +pgocaml_description_3 We are using Ot_spinner to display the list, which means that, in the case the page is generated client-side, the page will be displayed immediately with a spinner, that will be replaced by the contents when ready. The code contains a 2s sleep to demonstrate the spinner. Nous utilisons Ot_spinner pour afficher la liste : dans le cas où une page est générée côté client, cette page est affichée immédiatement avec une icône de chargement qui sera remplacée par le contenu quand il sera prêt. Pour la démonstration, nous avons ajouté une pause de 2s pour laisser le temps de voir l'icône de chargement. +pgocaml_users Users: Utilisateurs : +popup Popup Button Bouton popup +popup_click Click for a popup! Cliquez pour afficher un popup ! +popup_content Here is a button showing a simple popup window when clicked: Voici un bouton affichant une simple fenêtre popup quand vous cliquez dessus : +popup_message Popup message Message du popup +pull_to_refresh Pull to refresh Tirer pour rafraîchir +pull_to_refresh_1 This is an example of a page with refreshable content. It is a very common feature in mobile applications. You will need to view this page on your phone to see it work. Cette démo présente une page avec du contenu actualisable. C'est une fonctionnalité très présente dans les applications mobiles. Pour voir les effets de cette page, ouvrez-la dans l'application mobile. +pull_to_refresh_2 This page contains a counter that increases every time you "refresh" by pulling down the page. This feature is called "pull to refresh", but you give it your own action to be performed after the motion. Here, it updates a reactive signal after a second, but in your application, you will probably fetch data and update a more complicated signal than a number to rebuild a part of or the whole page, or do anything else you want. Cette page contient un compteur qui s'incrémente chaque fois que vous "rechargez" la page en tirant vers le bas avec votre doigt. Cette fonctionnalité s'appelle "Tirer pour rafraîchir", mais vous fournissez votre propre action à effectuer à la fin du geste. Ici, nous mettons un simple signal réactif à jour, mais dans votre application, vous récupérerez probablement des données depuis le serveur pour mettre à jour un signal plus compliqué qu'un nombre pour reconstruire toute ou une partie de la page, ou faire ce que vous voulez d'autre. +pull_to_refresh_counter You refreshed the page {{n}} times. Vous avez rafraîchi la page {{n}} fois. +reactive_programming Reactive pages Pages réactives +reactive_programming_1 This is an example of a page with reactive content. It is a very convenient solution to update pages when data changes. Ceci est un exemple d'une page avec du contenu réactif. C'est une solution extrêment simple pour mettre à jour une page quand les données changent. +reactive_programming_2 It defines a (client-side) reactive OCaml list. You can add elements in this list via the input form. The page is updated automatically when the value of the reactive list changes. Il définit une liste OCaml réactive (côté client). Vous pouvez ajouter des élements dans cette liste via le formulaire. Cette page sera automatique mise à jour quand la valeur de la liste réactive changera. +reactive_programming_3 The reactive page is generated either server-side (for example when you are using a Web browser and you reload this page) or client-side (in a mobile app or if you were already in the app before coming to this page). La page réactive est générée aussi bien du côté serveur (par exemple quand vous utilisez le navigateur web et que vous rechargez cette page) que du côté client (sur mobile ou si vous étiez déjà sur dans cette application avant de venir sur cette page). +reactive_programming_button add ajouter +rpc_button RPC button Bouton RPC +rpc_button_click_increase Click to increase server-side value Cliquez pour augmenter la valeur côté serveur. +rpc_button_description This button performs an RPC to increase a server-side value. Ce bouton réalise un appel de fonction distante (RPC) pour augmenter une valeur côté serveur. +saturday Saturday Samedi +send_message send message envoyer le message +services Services Services +services_1 Have a look at file {{f1}} to see some examples of service definitions. Most service handlers are defined in file {{f2}}. Service registration is done in {{f3}}. Have a look to see how to define a service returning an application page, an action or a redirection, etc. Read Ocsigen's tutorials and Eliom's manual for more information about services. Vous trouverez des exemples de définition de services dans le fichier {{f1}}. La plupart des handlers de services sont définis dans le fichier {{f2}}. L'enregistrement des services est fait dans le fichier {{f3}}. Jetez-y un œil pour voir comment définir une nouvelle page pour cette application, une action, une redirection, etc. Lisez les tutoriels d'Ocsigen et le manuel d'Eliom pour plus d'informations sur les services. +spinner Spinner Icône de chargement +spinner_content_ready The content is ready. Le contenu est prêt. +spinner_description_1 We use this widget to integrate into the page an HTML block that takes a long time to produce, e.g., because of a slow server call. Nous utilisons ce widget pour intégrer dans notre page HTML un bloc qui prend un long moment à produire, par exemple, à cause d'un appel serveur lent. +spinner_description_2 A spinner is displayed, which is then replaced with the actual content when this content is ready. Une icône de chargement est d'abord affichée, puis remplacée par le vrai contenu quand celui-ci est prêt. +spinner_description_3 For the demo we just sleep for 5 seconds to simulate waiting for the content. Pour la démonstration, nous avons ajouté un délai de 5 secondes pour simuler l'attente du contenu. +spinner_description_ot This is a demo of the Ocsigen Toolkit spinner widget. Ceci est la démonstration de l'icône de chargement d'Ocsigen Toolkit. +spinner_generated_client_side The spinner is generated client-side. L'icône de chargement est générée côté client. +spinner_message_replace_spinner This message has replaced the spinner. Ce message a remplacé l'icône de chargement. +static_files Static files Fichiers statiques +static_files_1 Use service {{static_dir}} (predefined in Eliom) to create links towards static files (images, fonts, etc.). Put static files you want to include in the mobile app in directory {{static}}. They will be stored locally on the mobile device. By default, links are relative on the Web app and absolute on the mobile app. For example, here is an example of an image stored locally in the mobile app: Utilisez le service {{static_dir}} (prédéfini dans Eliom) pour faire des liens vers des fichiers statiques (images, fontes, etc.). Les fichiers statiques que vous voulez inclure dans l'application mobile doivent être placés dans le répertoire {{static}}. Ils seront stockés en local sur l'appareil mobile. Par défaut les liens sont relatifs dans l'application Web et absolus dans l'application mobile. Forcez les liens relatifs pour faire des liens vers des fichiers locaux dans l'application mobile. Par exemple voici une image stockée localement dans l'application mobile : +static_files_2 and a remote image: et une image distante : +sunday Sunday Dimanche +the_module The module Le module +these_functions_called_server_or_client_side These functions can be called from either server- or client-side. Ces fonctions peuvent être appelées aussi bien côté client que côté serveur. +this_page_show_calendar This page shows Ocsigen Toolkit's date picker. Cette page montre le sélecteur de date d'Ocsigen Toolkit. +thursday Thursday Jeudi +timepicker Time picker Sélecteur d'heure +timepicker_back_to_hours Back to hours Revenir aux heures +timepicker_description This page shows the Ocsigen Toolkit's time picker. Cette page montre le sélecteur d'heure d'Ocsigen Toolkit. +tips Tips Astuces +tips1 Tips for new users and new features Astuces pour les nouveaux utilisateurs et nouvelles fonctionnalités +tips2 Module {{os_tips}} implements a way to display tips in the page to the users who haven't already seen them. Le module {{os_tips}} implémente une façon d'afficher des astuces dans la page aux utilisateurs qui ne les ont pas déjà vues. +tips3 This page contains a tip, that you will see only as connected user, until you close it. Cette page contient une astuce, que vous allez voir seulement en tant qu'utilisateur connecté, jusqu'à ce que vous la fermiez. +tips4 It is possible to reset the set of already seen tips from the {{set_page}}. Il est possible de réinitialiser l'ensemble des astuces déjà vues depuis la page {{set_page}}. +tips5 settings page page Paramètres +tongue_1 Tongue Languette +tuesday Tuesday Mardi +users Users Utilisateurs +wednesday Wednesday Mercredi +widget_feel_free Feel free to modify the generated code and use it or redistribute it as you want. Vous êtes libres de modifier le code généré et de l'utiliser ou de le redistribuer comme vous souhaitez. +widget_ot This app also contains demos for some widgets from Ocsigen Toolkit. Cette application contient également des démonstrations de quelques widgets d'Ocsigen Toolkit. +widget_see_drawer The different demos are accessible through the drawer menu. To open it click the top left button on the screen. Les différentes démonstrations sont accessibles à travers le menu. Pour l'ouvrir, cliquez sur le bouton en haut à gauche de l'écran. +you_are You are Vous êtes +you_are_not_connected You are not connected. Vous n'êtes pas connecté. +you_click_on_date You clicked on {{y}}/{{m}}/{{d}} Vous avez cliqué sur {{d}}/{{m}}/{{y}} +you_click_on_time You clicked on {{h}}:{{m}} Vous avez cliqué sur {{h}}:{{m}} +your_user_id Your user id Votre ID utilisateur diff --git a/daegsrv/assets/daegsrv_i18n.tsv b/daegsrv/assets/daegsrv_i18n.tsv new file mode 100644 index 0000000..8d18bb9 --- /dev/null +++ b/daegsrv/assets/daegsrv_i18n.tsv @@ -0,0 +1,64 @@ +welcome_text1 Welcome to Ocsigen Start. This is a template for applications based on Ocsigen (Eliom, Js_of_ocaml, etc.). Bienvenue dans Ocsigen Start ! Ceci est un template d'application écrite avec Ocsigen (Eliom, Js_of_ocaml, etc.). +welcome_text2 Use it: Utilisez-le : +welcome_text3 As a basis for your own applications. Comme point de départ pour vos propres applications ; +welcome_text4 To learn the most important concepts of client-server programming with Ocsigen. Pour apprendre les principaux concepts de la programmation client-serveur avec Ocsigen. +welcome_text5 This application contains: Cette application contient : +welcome_text6 Features for user management (log-in form, user registration, activation links, password recovery, settings page, etc.). Des fonctionnalités de gestion des utilisateurs (connexion, création d'utilisateur, liens d'activation, récupération de mot de passe, paramètres de l'utilisateur,...) ; +welcome_text7 An extensive demo of the most important features you need to implement your own app. Read the source code to learn! And remove the demo part when you're ready to start with your own app. Une démo des plus importantes fonctionnalités dont vous avez besoin pour écrire votre propre application. Lisez le code source pour apprendre ! Ensuite enlevez la partie demo quand vous êtes prêts à commencer votre propre application ; +welcome_text8 A library with useful features (tips, notifications, etc.). Une bibliothèque avec de nombeuses fonctionnalités utiles (tips, notifications, etc.) ; +welcome_text9 All the features you need to create a multilingual app. Tous les outils pour créer une application multilingue ; +welcome_text10 A basic responsive CSS. Une feuille de style "responsive" basique. +welcome_text11 This application is multi-platform: it can run as a client-server Web application (with server-side generated pages) and as a mobile app (with client-side generated pages) for Android, iOS or Windows. Have a look at the README file to learn how to generate the mobile apps, which you will be able to upload on Google Play or Apple App Store. Cette application est multi-plateforme : elle peut tourner comme application Web client-serveur (avec des pages générées côté serveur) ou bien comme application mobile pour iOS, Android ou Windows (avec des pages générées côté client). Regardez le fichier README pour apprendre comment générer les applications mobiles que vous pourrez envoyer sur Google Play ou Apple App Store. +about_handler_template This template provides a skeleton for an Ocsigen application. Ce template fournit une base pour une application Ocsigen. +about_handler_license Feel free to modify the generated code and use it or redistribute it in any way you want. Vous êtes libres de modifier le code généré et de l'utiliser ou le redistribuer comme vous le souhaitez. +footer_generated This application has been generated using the Cette application a été générée en utilisant le template d' +footer_eliom_distillery template for Eliom-distillery and uses the avec Eliom-distillery et utilise les technologies +footer_technology technology. . +home {{{capitalize?H||h}}}ome {{{capitalize?H||h}}}ome +about {{{capitalize?A||a}}}bout {{{capitalize?À||à}}} propos +demo Demo Démo +password {{{capitalize?P||p}}}assword {{{capitalize?M||m}}}ot de passe +retype_password retype your password retapez votre mot de passe +your_email {{{capitalize?Y||y}}}our email {{{capitalize?V||v}}}otre e-mail +your_password {{{capitalize?Y||y}}}our password {{{capitalize?V||v}}}otre mot de passe +keep_logged_in keep me logged in rester connecté +sign_in {{{capitalize?S||s}}}ign in {{{capitalize?S||s}}}e connecter +forgot_your_password_q {{{capitalize?F||f}}}orgot your password? {{{capitalize?M||m}}}ot de passe oublié ? +sign_up {{{capitalize?S||s}}}ign up {{{capitalize?S||s}}}'enregistrer +logout {{{capitalize?L||l}}}ogout {{{capitalize?S||s}}}e déconnecter +set_as_main_email {{{capitalize?S||s}}}et as main email {{{capitalize?D||d}}}éfinir comme e-mail principal +validated {{{capitalize?V||v}}}alidated {{{capitalize?V||v}}}alidé{{{f?e||}}} +waiting_confirmation {{{capitalize?W||w}}}aiting for confirmation {{{capitalize?E||e}}}n attente de confirmation +main_email {{{capitalize?M||m}}}ain email {{{capitalize?E||e}}}-mail principal +change_password {{{capitalize?C||c}}}hange your password: {{{capitalize?C||c}}}hanger votre mot de passe : +link_new_email Link a new email to your account: Ajouter une adresse e-mail à votre compte : +currently_registered_emails Currently registered emails: E-mails actuellement enregistrés : +settings {{{capitalize?S||s}}}ettings {{{capitalize?P||p}}}aramètres +error {{{capitalize?E||e}}}rror {{{capitalize?E||e}}}rreur +passwords_do_not_match Passwords do not match Les mots de passe ne correspondent pas +generate_action_link_key_subject_email creation création +sign_up_email_msg Welcome!\r\nTo confirm your email address, please click on this link: Bienvenue !\r\nPour confirmer votre adresse e-mail, cliquer sur ce lien : +email_already_exists Email already exists Cet e-mail existe déjà +user_does_not_exist user does not exist Cet utilisateur n'existe pas +account_not_activated Account not activated Ce compte n'est pas activé +wrong_password Wrong password Mauvais mot de passe +no_such_user No such user Cet utilisateur n'existe pas +add_email_msg Welcome!\r\nTo confirm your email address, please click on this link: Bienvenue !\r\nPour confirmer votre adresse e-mail, cliquez sur ce lien : +invalid_action_key Invalid action key, please ask for a new one. Clef d'action invalide. Demandez en une nouvelle svp. +forgot_pwd_email_msg Hi,\r\nTo set a new password, please click on this link: Bonjour,\r\nPour mettre à jour votre mot de passe, cliquez sur ce lien : +must_be_connected_to_see_page You must be connected to see this page. Vous devez être connecté pour voir cette page. +email_address Email address Adresse e-mail +your_first_name Your first name Votre prénom +your_last_name Your last name Votre nom +submit {{{capitalize?S||s}}}ubmit {{{capitalize?E||e}}}nvoyer +see_help_again_from_beginning See help again from beginning Revoir l'aide depuis le début +personal_information_not_set Your personal information has not been set yet. Vous n'avez pas encore entré vos données personnelles. +take_time_enter_name_password Please take time to enter your name and to set a password. Veuillez entrer votre nom et choisir un mot de passe svp. +wrong_data_fix Wrong data. Please fix. Données incorrectes. Veuillez corriger. +send {{{capitalize?S||s}}}end {{{capitalize?E||e}}}nvoyer +recover_password {{{capitalize?R||r}}}ecover password {{{capitalize?R||r}}}écupérer le mot de passe. +welcome {{{capitalize?W||w}}}elcome! {{{capitalize?B||b}}}ienvenue ! +log_in_to_see_page {{{capitalize?L||l}}}og in to see this page. {{{capitalize?C||c}}}onnectez-vous pour voir cette page. +change_profile_picture Change profile picture Changer votre photo de profil. +change_language Change language Changer la langue +disconnect_all Logout on all my devices Me déconnecter sur tous mes appareils diff --git a/daegsrv/assets/images/icon.png b/daegsrv/assets/images/icon.png new file mode 100644 index 0000000..7fefaa6 Binary files /dev/null and b/daegsrv/assets/images/icon.png differ diff --git a/daegsrv/daegsrv.conf.in b/daegsrv/daegsrv.conf.in new file mode 100644 index 0000000..ad50085 --- /dev/null +++ b/daegsrv/daegsrv.conf.in @@ -0,0 +1,82 @@ +%%% This is the template for your configuration file. The %%VALUES%% below are +%%% taken from the Makefile to generate the actual configuration files. +%%% This comment will disappear. + + + + %%PORT%% + %%% Only set for running, not for testing + %%USERGROUP%% + %%LOGDIR%% + %%DATADIR%% + utf-8 + /tmp + + %%% Only set when debugging + %%DEBUGMODE%% + + + %%CMDPIPE%% + + + + + + + + + + + + + %%% This will include the packages defined as SERVER_PACKAGES in your Makefile: + %%PACKAGES%% + + + %%ELIOM_MODULES%% + + + + + + + + +
+ + + + + + + diff --git a/daegsrv/daegsrv.eliom b/daegsrv/daegsrv.eliom new file mode 100644 index 0000000..bbacde7 --- /dev/null +++ b/daegsrv/daegsrv.eliom @@ -0,0 +1,76 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +let%client add_email_notif () = () + +let%server add_email_notif () = + if Eliom_reference.Volatile.get Os_user.user_already_exists + then Os_msg.msg ~level:`Err ~onload:true [%i18n S.email_already_exists] + +let%shared () = + (* Registering services. Feel free to customize handlers. *) + Eliom_registration.Action.register + ~service:Os_services.set_personal_data_service + Daegsrv_handlers.set_personal_data_handler; + Eliom_registration.Redirection.register + ~service:Os_services.set_password_service + Daegsrv_handlers.set_password_handler; + Eliom_registration.Action.register + ~service:Os_services.forgot_password_service + Daegsrv_handlers.forgot_password_handler; + Eliom_registration.Action.register ~service:Os_services.preregister_service + Daegsrv_handlers.preregister_handler; + Eliom_registration.Action.register ~service:Os_services.sign_up_service + Os_handlers.sign_up_handler; + Eliom_registration.Action.register ~service:Os_services.connect_service + Os_handlers.connect_handler; + Eliom_registration.Unit.register ~service:Os_services.disconnect_service + (Os_handlers.disconnect_handler ~main_page:true); + Eliom_registration.Any.register ~service:Os_services.action_link_service + (Os_session.Opt.connected_fun Daegsrv_handlers.action_link_handler); + Eliom_registration.Action.register ~service:Os_services.add_email_service + (fun () email -> + let%lwt () = Os_handlers.add_email_handler () email in + add_email_notif (); Lwt.return_unit); + Eliom_registration.Action.register + ~service:Os_services.update_language_service + Daegsrv_handlers.update_language_handler; + Daegsrv_base.App.register ~service:Os_services.main_service + (Daegsrv_page.Opt.connected_page + Daegsrv_handlers.main_service_handler); + Daegsrv_base.App.register + ~service:Daegsrv_services.about_service + (Daegsrv_page.Opt.connected_page + Daegsrv_handlers.about_handler); + Daegsrv_base.App.register + ~service:Daegsrv_services.settings_service + (Daegsrv_page.Opt.connected_page + Daegsrv_handlers.settings_handler) + +let%server () = + Eliom_registration.Ocaml.register + ~service:Daegsrv_services.upload_user_avatar_service + (Os_session.connected_fun + Daegsrv_handlers.upload_user_avatar_handler) + +(* Print more debugging information when is in config file + (DEBUG = yes in Makefile.options). + Example of use: + let section = Lwt_log.Section.make "Daegsrv:sectionname" + ... + Lwt_log.ign_info ~section "This is an information"; + (or ign_debug, ign_warning, ign_error etc.) + *) +let%server _ = + if Eliom_config.get_debugmode () + then ( + ignore + [%client + ((* Eliom_config.debug_timings := true; *) + (* Lwt_log_core.add_rule "eliom:client*" Lwt_log_js.Debug; *) + (* Lwt_log_core.add_rule "os*" Lwt_log_js.Debug; *) + Lwt_log_core.add_rule "Daegsrv*" Lwt_log_js.Debug + (* Lwt_log_core.add_rule "*" Lwt_log_js.Debug *) + : unit)]; + (* Lwt_log_core.add_rule "*" Lwt_log.Debug *) + Lwt_log_core.add_rule "Daegsrv*" Lwt_log.Debug) diff --git a/daegsrv/daegsrv.opam b/daegsrv/daegsrv.opam new file mode 100644 index 0000000..399dcae --- /dev/null +++ b/daegsrv/daegsrv.opam @@ -0,0 +1,10 @@ +opam-version: "2.0" +name: "daegsrv" +version: "0.1" +synopsis: "Pseudo package for defining project dependencies" + +depends: [ + "eliom" {>= "10.0.0" & < "11.0.0"} + "ocsipersist-pgsql" {>= "1.0" & < "2.0"} + "ocsigen-start" +] diff --git a/daegsrv/daegsrv.sql b/daegsrv/daegsrv.sql new file mode 100644 index 0000000..4ba26e9 --- /dev/null +++ b/daegsrv/daegsrv.sql @@ -0,0 +1,148 @@ +-- README: +-- Do not remove the field with a `-- DEFAULT` suffix. +-- That's the default tables/fields needed by Ocsigen-start + +CREATE DATABASE ocsipersist_daegsrv; + +CREATE EXTENSION citext; --DEFAULT +-- You may remove the above line if you use the type TEXT for emails instead of CITEXT + +CREATE SCHEMA ocsigen_start + + -- Note that `main_email` is not an `emails` foreign key to prevent a circular + -- dependency. Triggers on table `emails` defined below make sure this column + -- stays in sync + CREATE TABLE users ( -- DEFAULT + userid bigserial primary key, -- DEFAULT + firstname text NOT NULL, + lastname text NOT NULL, + main_email citext, + password text, + avatar text, + language text + ) + + CREATE TABLE emails ( -- DEFAULT + email citext primary key, -- DEFAULT + userid bigint NOT NULL references users(userid), -- DEFAULT + validated boolean NOT NULL DEFAULT(false) + ) + + CREATE TABLE activation ( -- DEFAULT + activationkey text primary key, -- DEFAULT + userid bigint NOT NULL references users(userid), -- DEFAULT + email citext NOT NULL, + autoconnect boolean NOT NULL, + validity bigint NOT NULL default 1, + action text NOT NULL, + data text NOT NULL, + creationdate timestamp NOT NULL default (now() at time zone 'utc'), + expiry timestamp + ) + + CREATE TABLE groups ( -- DEFAULT + groupid bigserial primary key, -- DEFAULT + name text UNIQUE NOT NULL, -- DEFAULT + description text -- DEFAULT + ) + + CREATE TABLE user_groups ( -- DEFAULT + userid bigint NOT NULL references users(userid), -- DEFAULT + groupid bigint NOT NULL references groups(groupid) -- DEFAULT + ) + + CREATE TABLE preregister ( + email citext NOT NULL + ) + + CREATE TABLE phones ( + number citext primary key, + userid bigint NOT NULL references users(userid) + ); + + +CREATE OR REPLACE FUNCTION can_delete_email () + RETURNS TRIGGER AS $$ + BEGIN + IF (EXISTS (SELECT 1 + FROM ocsigen_start.emails, ocsigen_start.users + WHERE emails.userid = old.userid + AND users.userid = old.userid + AND emails.email <> old.email + AND users.main_email = emails.email + AND validated)) + THEN + RETURN old; + ELSE + RETURN NULL; + END IF; + END; + $$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION can_delete_phone () + RETURNS TRIGGER AS $$ + BEGIN + IF (EXISTS (SELECT 1 + FROM ocsigen_start.phones + WHERE userid = old.userid AND number <> old.number) OR + EXISTS (SELECT 1 + FROM ocsigen_start.emails + WHERE userid = old.userid + AND validated)) + THEN + RETURN old; + ELSE + RETURN NULL; + END IF; + END; + $$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION set_main_email () + RETURNS TRIGGER AS $$ + BEGIN + IF (EXISTS (SELECT 1 + FROM ocsigen_start.users + WHERE users.userid = NEW.userid + AND (users.main_email IS NULL OR + users.main_email NOT SIMILAR TO '%@%'))) + THEN + UPDATE users + SET main_email = NEW.email WHERE users.userid = NEW.userid; + END IF; + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + + CREATE OR REPLACE + FUNCTION trigger_exists (t_name text) + RETURNS boolean + STABLE AS $$ + SELECT EXISTS + (SELECT 1 FROM pg_trigger + WHERE NOT tgisinternal + AND tgname = t_name) + $$ LANGUAGE SQL; + + +DO $$ + BEGIN + IF NOT trigger_exists('can_delete_phone') THEN + CREATE TRIGGER can_delete_phone + BEFORE DELETE on ocsigen_start.phones + FOR EACH ROW + EXECUTE PROCEDURE can_delete_phone(); + END IF; + IF NOT trigger_exists('can_delete_email') THEN + CREATE TRIGGER can_delete_email + BEFORE DELETE on ocsigen_start.emails + FOR EACH ROW + EXECUTE PROCEDURE can_delete_email(); + END IF; + IF NOT trigger_exists('set_main_email') THEN + CREATE TRIGGER set_main_email + AFTER INSERT on ocsigen_start.emails + FOR EACH ROW + EXECUTE PROCEDURE set_main_email(); + END IF; + END; +$$; diff --git a/daegsrv/daegsrv_base.eliom b/daegsrv/daegsrv_base.eliom new file mode 100644 index 0000000..c09adda --- /dev/null +++ b/daegsrv/daegsrv_base.eliom @@ -0,0 +1,37 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +let%server application_name = !Daegsrv_config.app_name +let%client application_name = Eliom_client.get_application_name () +let%shared displayed_app_name = "daegsrv" + +(* Database initialization *) +let () = + Os_db.init + ?host:!Daegsrv_config.os_db_host + ?port:!Daegsrv_config.os_db_port + ?user:!Daegsrv_config.os_db_user + ?password:!Daegsrv_config.os_db_password + ?database:!Daegsrv_config.os_db_database + ?unix_domain_socket_dir: + !Daegsrv_config.os_db_unix_domain_socket_dir + () + +let () = Os_email.set_mailer "/usr/sbin/sendmail" + +let () = + Os_email.set_from_addr ("daegsrv team", "noreply@DEFAULT.DEFAULT") + +(* Create a module for the application. See + https://ocsigen.org/eliom/manual/clientserver-applications for more + information. *) +[%%shared +module App = Eliom_registration.App (struct + let application_name = application_name + let global_data_path = Some ["__global_data__"] +end)] + +(* As the headers (stylesheets, etc) won't change, we ask Eliom not to + update the of the page when changing page. (This also avoids + blinking when changing page in iOS). *) +let%client _ = Eliom_client.persist_document_head () diff --git a/daegsrv/daegsrv_config.eliom b/daegsrv/daegsrv_config.eliom new file mode 100644 index 0000000..d97e604 --- /dev/null +++ b/daegsrv/daegsrv_config.eliom @@ -0,0 +1,74 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +(* This module contains the configuration of your Eliom application. + You can take some configuration options from Ocsigen server's + configuration file, as shown below. + + See https://ocsigen.org/ocsigenserver/ for more information about + the configuration file and how to get the information of the + config file in an Eliom project. *) + +(* Variable definitions *) + +(* The following variables are changed by the ocsigenserver + configuration file. *) + +(* Configuration of the application itself. *) + +let app_name = ref "" +let css_name = ref "" + +(* The name of the avatar directory. *) +let avatar_dir = ref [] + +(* Database configuration. *) + +let os_db_host = ref None +let os_db_port = ref None +let os_db_user = ref None +let os_db_password = ref None +let os_db_database = ref None +let os_db_unix_domain_socket_dir = ref None + +(* Get variables values from the ocsigenserver configuration file *) + +(* Application configuration *) +let app = + let open Ocsigen_extensions.Configuration in + let attributes = + [ attribute ~name:"name" ~obligatory:true (fun h -> app_name := h) + ; attribute ~name:"css" ~obligatory:true (fun h -> css_name := h) ] + in + element ~name:"app" ~obligatory:true ~attributes () + +(* Avatars configuration *) +let avatars = + let open Ocsigen_extensions.Configuration in + let attributes = + [ attribute ~name:"dir" ~obligatory:true (fun h -> + avatar_dir := Eliom_lib.String.split '/' h) ] + in + element ~name:"avatars" ~obligatory:true ~attributes () + +(* Database configuration *) +let os_db = + let open Ocsigen_extensions.Configuration in + let attributes = + [ attribute ~name:"host" (fun h -> os_db_host := Some h) + ; attribute ~name:"port" (fun h -> + os_db_port := + try Some (int_of_string h) + with Failure _ -> + raise + @@ Ocsigen_extensions.Error_in_config_file + "port is not an integer") + ; attribute ~name:"user" (fun h -> os_db_user := Some h) + ; attribute ~name:"password" (fun h -> os_db_password := Some h) + ; attribute ~name:"database" (fun h -> os_db_database := Some h) + ; attribute ~name:"unix_domain_socket_dir" (fun h -> + os_db_unix_domain_socket_dir := Some h) ] + in + element ~name:"os-db" ~attributes () + +let _ = Eliom_config.parse_config [app; avatars; os_db] diff --git a/daegsrv/daegsrv_config.eliomi b/daegsrv/daegsrv_config.eliomi new file mode 100644 index 0000000..82f31d2 --- /dev/null +++ b/daegsrv/daegsrv_config.eliomi @@ -0,0 +1,15 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +val app_name : string ref +val css_name : string ref +val avatar_dir : string list ref +val os_db_host : string option ref +val os_db_port : int option ref +val os_db_user : string option ref +val os_db_password : string option ref +val os_db_database : string option ref +val os_db_unix_domain_socket_dir : string option ref +val app : Ocsigen_extensions.Configuration.element +val avatars : Ocsigen_extensions.Configuration.element +val os_db : Ocsigen_extensions.Configuration.element diff --git a/daegsrv/daegsrv_container.eliom b/daegsrv/daegsrv_container.eliom new file mode 100644 index 0000000..7d0166d --- /dev/null +++ b/daegsrv/daegsrv_container.eliom @@ -0,0 +1,90 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +let%shared os_header ?user () = + let open Eliom_content.Html.F in + let%lwt user_box = + Os_user_view.user_box ~a_placeholder_email:[%i18n S.your_email] + ~a_placeholder_pwd:[%i18n S.your_password] + ~text_keep_me_logged_in:[%i18n S.keep_logged_in] + ~content_popup_forgotpwd:[%i18n S.recover_password ~capitalize:true] + ~text_button_forgotpwd:[%i18n S.forgot_your_password_q ~capitalize:true] + ~text_sign_in:[%i18n S.sign_in ~capitalize:true] + ~text_sign_up:[%i18n S.sign_up ~capitalize:true] + ~text_send_button:[%i18n S.send ~capitalize:true] ?user () + in + Lwt.return + (header + ~a:[a_class ["os-page-header"]] + [ a + ~a:[a_class ["os-page-header-app-name"]] + ~service:Os_services.main_service + [txt Daegsrv_base.displayed_app_name] + () + ; user_box ]) + +let%shared os_footer () = + let open Eliom_content.Html.F in + footer + ~a:[a_class ["os-page-footer"]] + [ p + [ txt [%i18n S.footer_generated] + ; a ~service:Daegsrv_services.os_github_service + [txt " Ocsigen Start "] () + ; txt [%i18n S.footer_eliom_distillery] + ; a ~service:Daegsrv_services.ocsigen_service + [txt " Ocsigen "] () + ; txt [%i18n S.footer_technology] ] ] + +let%rpc get_wrong_pdata () + : ((string * string) * (string * string)) option Lwt.t + = + Lwt.return @@ Eliom_reference.Volatile.get Os_msg.wrong_pdata + +let%shared connected_welcome_box () = + let open Eliom_content.Html.F in + let%lwt wrong_pdata = get_wrong_pdata () in + let info, ((fn, ln), (p1, p2)) = + match wrong_pdata with + | None -> + ( p + [ txt [%i18n S.personal_information_not_set] + ; br () + ; txt [%i18n S.take_time_enter_name_password] ] + , (("", ""), ("", "")) ) + | Some wpd -> p [txt [%i18n S.wrong_data_fix]], wpd + in + Lwt.return + @@ div + ~a:[a_class ["os-welcome-box"]] + [ div [h2 [%i18n welcome ~capitalize:true]; info] + ; Os_user_view.information_form + ~a_placeholder_password:[%i18n S.password] + ~a_placeholder_retype_password:[%i18n S.retype_password] + ~a_placeholder_firstname:[%i18n S.your_first_name] + ~a_placeholder_lastname:[%i18n S.your_last_name] + ~text_submit:[%i18n S.submit] ~firstname:fn ~lastname:ln + ~password1:p1 ~password2:p2 () ] + +let%shared get_user_data = function + | None -> Lwt.return_none + | Some myid -> + let%lwt u = Os_user_proxy.get_data myid in + Lwt.return_some u + +let%shared page ?html_a ?a ?title ?head myid_o content = + let%lwt me = get_user_data myid_o in + let%lwt content = + match me with + | Some me when not (Os_user.is_complete me) -> + let%lwt cwb = connected_welcome_box () in + Lwt.return @@ (cwb :: content) + | _ -> Lwt.return @@ content + in + let%lwt h = os_header ?user:me () in + Lwt.return + (Os_page.content ?html_a ?a ?title ?head + [ h + ; Eliom_content.Html.F.(div ~a:[a_class ["os-body"]] content) + ; os_footer () + ; Daegsrv_drawer.make ?user:me () ]) diff --git a/daegsrv/daegsrv_container.eliomi b/daegsrv/daegsrv_container.eliomi new file mode 100644 index 0000000..ec8dfd6 --- /dev/null +++ b/daegsrv/daegsrv_container.eliomi @@ -0,0 +1,42 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +[%%shared.start] + +(** This module defines the default template for application pages *) + +val os_header + : ?user:Os_types.User.t + -> unit + -> [> `Header] Eliom_content.Html.F.elt Lwt.t +(** [os_header ?user ()] defines the header for all pages. In this + template, it's a userbox and the user name is displayed. *) + +val os_footer : unit -> [> `Footer] Eliom_content.Html.F.elt +(** [os_footer ()] defines a footer for the page. *) + +val connected_welcome_box + : unit + -> [> Html_types.div] Eliom_content.Html.F.elt Lwt.t + +val get_user_data : Os_types.User.id option -> Os_types.User.t option Lwt.t + +val page + : ?html_a:Html_types.html_attrib Eliom_content.Html.attrib list + -> ?a:Html_types.body_attrib Eliom_content.Html.attrib list + -> ?title:string + -> ?head:[< Html_types.head_content_fun] Eliom_content.Html.elt list + -> Os_types.User.id option + -> [< Html_types.div_content_fun > `Div] Eliom_content.Html.F.elt + Eliom_content.Html.F.list_wrap + -> Os_page.content Lwt.t +(** [page userid_o content] returns a page personalized for the user + with id [myid_o] and with the content [content]. It adds a header, + a footer, and a drawer menu. If the user profile is not + completed, a connected welcome box is added. *) + +[%%shared.start] + +val get_wrong_pdata + : unit + -> ((string * string) * (string * string)) option Lwt.t diff --git a/daegsrv/daegsrv_drawer.eliom b/daegsrv/daegsrv_drawer.eliom new file mode 100644 index 0000000..2663ed1 --- /dev/null +++ b/daegsrv/daegsrv_drawer.eliom @@ -0,0 +1,39 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +[%%shared open Eliom_content.Html.F] + +(** This module defines the drawer menu *) + +let%shared item text service = + li [a ~a:[a_class ["os-drawer-item"]] ~service [txt text] ()] + +let%shared user_menu () = + [ item + [%i18n S.settings ~capitalize:true] + Daegsrv_services.settings_service + ; Eliom_content.Html.F.li + [ Os_user_view.disconnect_link + ~text_logout:[%i18n S.logout ~capitalize:true] + ~a:[a_class ["os-drawer-item"]] + () ] ] + +let%shared make ?user () = + let items = if user = None then [] else user_menu () in + let items = + item [%i18n S.home ~capitalize:true] Os_services.main_service + :: item [%i18n S.about ~capitalize:true] + Daegsrv_services.about_service + :: Demo_tools.drawer_contents () + :: items + in + let menu = ul ~a:[a_class ["os-drawer-menu"]] items in + let contents = + match user with + | None -> [menu] + | Some user -> + let user_box = Os_user_view.connected_user_box ~user in + [user_box; menu] + in + let drawer, _, _ = Ot_drawer.drawer contents in + drawer diff --git a/daegsrv/daegsrv_handlers.eliom b/daegsrv/daegsrv_handlers.eliom new file mode 100644 index 0000000..caed8a4 --- /dev/null +++ b/daegsrv/daegsrv_handlers.eliom @@ -0,0 +1,181 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +[%%shared open Eliom_content.Html.F] + +(* Upload user avatar *) +let upload_user_avatar_handler myid () ((), (cropping, photo)) = + let avatar_dir = + List.fold_left Filename.concat + (List.hd !Daegsrv_config.avatar_dir) + (List.tl !Daegsrv_config.avatar_dir) + in + let%lwt avatar = + Os_uploader.record_image avatar_dir ~ratio:1. ?cropping photo + in + let%lwt user = Os_user.user_of_userid myid in + let old_avatar = Os_user.avatar_of_user user in + let%lwt () = Os_user.update_avatar ~userid:myid ~avatar in + match old_avatar with + | None -> Lwt.return_unit + | Some old_avatar -> Lwt_unix.unlink (Filename.concat avatar_dir old_avatar) + +(* Set personal data *) + +let%server set_personal_data_handler = + Os_session.connected_fun Os_handlers.set_personal_data_handler + +let%rpc set_personal_data_rpc (data : (string * string) * (string * string)) + : unit Lwt.t + = + set_personal_data_handler () data + +let%client set_personal_data_handler () = set_personal_data_rpc + +(* Forgot password *) + +let%server forgot_password_handler = + Os_handlers.forgot_password_handler + Daegsrv_services.settings_service + +let%rpc forgot_password_rpc (email : string) : unit Lwt.t = + forgot_password_handler () email + +let%client forgot_password_handler () = forgot_password_rpc + +(* Action links are links created to perform an action. They are used + for example to send activation links by email, or links to reset a + password. You can create your own action links and define their + behavior here. *) +let%shared action_link_handler myid_o akey () = + (* We try first the default actions (activation link, reset + password) *) + try%lwt Os_handlers.action_link_handler myid_o akey () with + | Os_handlers.No_such_resource | Os_handlers.Invalid_action_key _ -> + Os_msg.msg ~level:`Err ~onload:true [%i18n S.invalid_action_key]; + Eliom_registration.(appl_self_redirect Action.send) () + | e -> + let%lwt email, phantom_user = + match e with + | Os_handlers.Account_already_activated_unconnected + { Os_types.Action_link_key.userid = _ + ; email + ; validity = _ + ; action = _ + ; data = _ + ; autoconnect = _ } -> + Lwt.return (email, false) + | Os_handlers.Custom_action_link + ( { Os_types.Action_link_key.userid = _ + ; email + ; validity = _ + ; action = _ + ; data = _ + ; autoconnect = _ } + , phantom_user ) -> + Lwt.return (email, phantom_user) + | _ -> Lwt.fail e + in + (* Define here your custom action links. If phantom_user is true, + it means the link has been created for an email that does not + correspond to an existing user. By default, we just display a + sign up form or phantom users, a login form for others. You + don't need to modify this if you are not using custom action + links. + + Perhaps personalise the intended behavior for when you meet + [Account_already_activated_unconnected]. *) + if myid_o = None (* Not currently connected, and no autoconnect *) + then + if phantom_user + then + let page = + [ div + ~a:[a_class ["login-signup-box"]] + [ Os_user_view.sign_up_form + ~a_placeholder_email:[%i18n S.your_email] + ~text:[%i18n S.sign_up] ~email () ] ] + in + Daegsrv_base.App.send + (Daegsrv_page.make_page (Os_page.content page)) + else + let page = + [ div + ~a:[a_class ["login-signup-box"]] + [ Os_user_view.connect_form + ~a_placeholder_email:[%i18n S.your_email] + ~a_placeholder_pwd:[%i18n S.your_password] + ~text_keep_me_logged_in:[%i18n S.keep_logged_in] + ~text_sign_in:[%i18n S.sign_in] ~email () ] ] + in + Daegsrv_base.App.send + (Daegsrv_page.make_page (Os_page.content page)) + else + (*VVV In that case we must do something more complex. Check + whether myid = userid and ask the user what he wants to + do. *) + let open Eliom_registration in + appl_self_redirect Redirection.send + (Redirection Eliom_service.reload_action) + +(* Set password *) + +let%server set_password_handler = + Os_session.connected_fun (fun myid () (pwd, pwd2) -> + let%lwt () = Os_handlers.set_password_handler myid () (pwd, pwd2) in + Lwt.return (Eliom_registration.Redirection Eliom_service.reload_action)) + +let%client set_password_handler () (pwd, pwd2) = + let%lwt () = Os_handlers.set_password_rpc (pwd, pwd2) in + Lwt.return (Eliom_registration.Redirection Eliom_service.reload_action) + +(* Preregister *) + +let%server preregister_handler = Os_handlers.preregister_handler + +let%rpc preregister_rpc (email : string) : unit Lwt.t = + preregister_handler () email + +let%client preregister_handler () = preregister_rpc + +let%shared main_service_handler myid_o () () = + Daegsrv_container.page + ~a:[a_class ["os-page-main"]] + myid_o + [ p [%i18n welcome_text1] + ; p [%i18n welcome_text2] + ; ul [li [%i18n welcome_text3]; li [%i18n welcome_text4]] + ; p [%i18n welcome_text5] + ; ul + [ li [%i18n welcome_text6] + ; li [%i18n welcome_text7] + ; li [%i18n welcome_text8] + ; li [%i18n welcome_text9] + ; li [%i18n welcome_text10] ] + ; p [%i18n welcome_text11] ] + +let%shared about_handler myid_o () () = + let open Eliom_content.Html.F in + Daegsrv_container.page + ~a:[a_class ["os-page-about"]] + myid_o + [ div + [ p [%i18n about_handler_template] + ; br () + ; p [%i18n about_handler_license] ] ] + +let%shared settings_handler myid_o () () = + let%lwt content = + match myid_o with + | Some _ -> Daegsrv_settings.settings_content () + | None -> Lwt.return [p [%i18n log_in_to_see_page ~capitalize:true]] + in + Daegsrv_container.page myid_o content + +let%server update_language_handler () language = + Os_session.connected_wrapper Daegsrv_language.update_language + (Daegsrv_i18n.language_of_string language) + +let%client update_language_handler () language = + Daegsrv_i18n.(set_language (language_of_string language)); + Os_current_user.update_language language diff --git a/daegsrv/daegsrv_handlers.eliomi b/daegsrv/daegsrv_handlers.eliomi new file mode 100644 index 0000000..3071ff8 --- /dev/null +++ b/daegsrv/daegsrv_handlers.eliomi @@ -0,0 +1,85 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +(** This module defines handlers to upload avatar, upload personal + data, set a new password, and also main handlers (main page, about + page, and settings page). In addition to including all default + handlers from OS (see {!Os_handlers}), it overrides some of them + for the purposes of this template. *) + +[%%server.start] + +val upload_user_avatar_handler + : Os_types.User.id + -> unit + -> unit + * ((float * float * float * float) option * Ocsigen_extensions.file_info) + -> unit Lwt.t +(** Update new user avatar with cropping option. The new avatar is saved + and the old one is removed. *) + +[%%shared.start] + +val set_personal_data_handler + : unit + -> (string * string) * (string * string) + -> unit Lwt.t +(** Update personal data. It uses the default OS handler + {!Os_handlers.set_personal_data_handler} and gets the user information + with {!Os_session.connected_fun}. *) + +val forgot_password_handler : unit -> string -> unit Lwt.t +(** Reset forgotten password. It uses the default OS handler + {!Os_handlers.forgot_password_handler} with the main service. *) + +val action_link_handler + : Os_types.User.id option + -> string + -> unit + -> Daegsrv_base.App.result Lwt.t + +val set_password_handler + : unit + -> string * string + -> Eliom_service.non_ocaml Eliom_registration.redirection Lwt.t +(** Set a new password. It uses the default OS handler + {!Os_handlers.set_password_handler} and gets the user information + with {!Os_session.connected_fun}. *) + +val preregister_handler : unit -> string -> unit Lwt.t + +(** The following functions are the handlers for the three main pages. + They are created with {!Daegsrv_container.page} which + means that a header and a footer will be displayed in addition to + the main content. + + For each of them, you can personalize the page for a specific user + by sending the userid as first parameter. *) + +val main_service_handler + : Os_types.User.id option + -> unit + -> unit + -> Os_page.content Lwt.t +(** The first page of the application *) + +val about_handler + : Os_types.User.id option + -> unit + -> unit + -> Os_page.content Lwt.t +(** About page *) + +val settings_handler + : Os_types.User.id option + -> unit + -> unit + -> Os_page.content Lwt.t +(** Settings page. If the user is connected (see + {!Daegsrv_container.get_user_data}), a settings + container will be created. *) + +val update_language_handler + : unit + -> string + -> Eliom_registration.Action.page Lwt.t diff --git a/daegsrv/daegsrv_icons.eliom b/daegsrv/daegsrv_icons.eliom new file mode 100644 index 0000000..4d39086 --- /dev/null +++ b/daegsrv/daegsrv_icons.eliom @@ -0,0 +1,38 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +[%%shared.start] + +(** This module defines an interface to create icons HTML element with + predefined style/value. We assume "Font Awesome" icons are used by + default (fa CSS class is added when using [icon classes]). See + http://fontawesome.io/ for more information and for the complete + list of CSS classes values. *) + +module Make (A : module type of Eliom_content.Html.F) = struct + (** [icon classes ~a:other_css_classes ()] create an icon HTML + attribute with "fa" and [classes] as CSS classes. The HTML tag + "i" is used because it is the de facto standard for icons. The + optional parameter ~a is at the end to be able to add other CSS + classes with predefined icons. *) + let icon classes + ?(a = ([] : Html_types.i_attrib Eliom_content.Html.attrib list)) () + = + A.i ~a:(A.a_class ("fa" :: classes) :: a) [] + + (** Icons used by Ocsigen Start's library *) + + let user = icon ["fa-user"; "fa-fw"] + let signout = icon ["fa-sign-out"; "fa-fw"] + let close = icon ["fa-close"; "fa-fw"] + let trash = icon ["fa-trash-o"; "fa-fw"] + + (* Add your own icons here. See http://fontawesome.io/icons/ for the + complete list of CSS classes available by default. *) +end + +module F = Make (Eliom_content.Html.F) +module D = Make (Eliom_content.Html.D) + +(* Register this module for use by Os_icon. *) +module Empty = Os_icons.Register (F) (D) diff --git a/daegsrv/daegsrv_language.eliom b/daegsrv/daegsrv_language.eliom new file mode 100644 index 0000000..18d3985 --- /dev/null +++ b/daegsrv/daegsrv_language.eliom @@ -0,0 +1,58 @@ +(* This file was generated by Ocsigen-start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +let%server best_matched_language () = + (* lang contains a list of (language_as_string, quality_value) *) + let lang = Eliom_request_info.get_accept_language () in + (* If no quality is given, we suppose it's 1 *) + let lang = + List.map (fun (s, q) -> s, match q with Some q -> q | None -> 1.) lang + in + (* Increasingly sort based on the quality *) + let lang = List.sort (fun (_, q1) (_, q2) -> compare q2 q1) lang in + Lwt.return + @@ + (* The first language of the list is returned. If the list is empty, + the default language is returned. *) + let rec aux = function + | (l, _) :: tl -> ( + try Daegsrv_i18n.guess_language_of_string l + with Daegsrv_i18n.Unknown_language _ -> aux tl) + | [] -> Daegsrv_i18n.default_language + in + aux lang + +let%server update_language lang = + let language = Daegsrv_i18n.string_of_language lang in + let myid_o = Os_current_user.Opt.get_current_userid () in + (* Update the server and client values *) + Daegsrv_i18n.set_language lang; + ignore [%client (Daegsrv_i18n.set_language ~%lang : unit)]; + (* Update in the database if a user is connected *) + match myid_o with + | None -> Lwt.return_unit + | Some userid -> Os_user.update_language ~userid ~language + +let%server _ = + Os_session.on_start_process (fun _ -> + (* Guess a default language. *) + let%lwt lang = best_matched_language () in + ignore (update_language lang); + Lwt.return_unit); + Os_session.on_start_connected_process (fun userid -> + (* Set language according to user preferences. *) + let%lwt language = + match%lwt Os_user.get_language userid with + | Some lang -> + Lwt.return (Daegsrv_i18n.guess_language_of_string lang) + | None -> + let%lwt best_language = best_matched_language () in + ignore + (Os_user.update_language ~userid + ~language: + (Daegsrv_i18n.string_of_language best_language)); + Lwt.return best_language + in + Daegsrv_i18n.set_language language; + ignore [%client (Daegsrv_i18n.set_language ~%language : unit)]; + Lwt.return_unit) diff --git a/daegsrv/daegsrv_language.eliomi b/daegsrv/daegsrv_language.eliomi new file mode 100644 index 0000000..a806b2b --- /dev/null +++ b/daegsrv/daegsrv_language.eliomi @@ -0,0 +1,14 @@ +(* This file was generated by Ocsigen-start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +[%%server.start] + +(** This module is used for i18n (internationalization). I18n allows + to have an application in multiple languages. The rule [make + i18n-update] uses this module to create the i18n file for + translations (see [Makefile.options]). *) + +val update_language : Daegsrv_i18n.t -> unit Lwt.t +(** [update_language language] updates the language (client and server + side) for the current user with the value [language]. It also + updates the value in the database if an user is connected. *) diff --git a/daegsrv/daegsrv_mobile.eliom b/daegsrv/daegsrv_mobile.eliom new file mode 100644 index 0000000..6a02c47 --- /dev/null +++ b/daegsrv/daegsrv_mobile.eliom @@ -0,0 +1,159 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +[%%client.start] + +[@@@ocaml.warning "-33"] + +open Daegsrv (* for dependency reasons *) + +[@@@ocaml.warning "+33"] + +[%%client open Js_of_ocaml] +[%%client open Js_of_ocaml_lwt] + +(* This RPC is called when client application is initialized. This + way, the server sends necessary cookies to the client (the mobile + app) early on and subsequent requests from the client will contain + the proper cookies. + + The RPC only initializes Os_date by default, but you can add your + own actions to be performed server side on first client request, if + necessary. *) +let%rpc init_request myid_o (tz : string) : unit Lwt.t = + ignore myid_o; Os_date.initialize tz; Lwt.return_unit + +let to_lwt f = + let wait, wakeup = Lwt.wait () in + f (Lwt.wakeup wakeup); + wait + +let ondeviceready = + to_lwt (fun cont -> + ignore + @@ Js_of_ocaml.Dom.addEventListener Js_of_ocaml.Dom_html.document + (Js_of_ocaml.Dom_html.Event.make "deviceready") + (Js_of_ocaml.Dom_html.handler (fun _ -> + cont (); Js_of_ocaml.Js._true)) + Js_of_ocaml.Js._false) + +let app_started = ref false +let initial_change_page = ref None + +let change_page_gen action = + if !app_started + then Lwt.async action + else if !initial_change_page = None + then initial_change_page := Some action + +let change_page_uri uri = + change_page_gen (fun () -> Eliom_client.change_page_uri uri) + +let handle_initial_url () = + let tz = Os_date.user_tz () in + let%lwt () = init_request tz in + let%lwt () = ondeviceready in + app_started := true; + match !initial_change_page with + | None -> + Eliom_client.change_page ~replace:true ~service:Os_services.main_service + () () + | Some action -> action () + +let () = + Lwt.async @@ fun () -> + if Eliom_client.is_client_app () + then ( + (* Initialize the application server-side; there should be a + single initial request for that. *) + Os_date.disable_auto_init (); + let%lwt _ = Lwt_js_events.onload () in + handle_initial_url ()) + else Lwt.return_unit + +(* Reactivate comet on resume and online events *) + +let () = + Firebug.console##log (Js_of_ocaml.Js.string "adding resume/online listeners"); + let activate ev = + ignore + @@ Js_of_ocaml.Dom.addEventListener Js_of_ocaml.Dom_html.document + (Js_of_ocaml.Dom_html.Event.make ev) + (Js_of_ocaml.Dom_html.handler (fun _ -> + Firebug.console##log (Js_of_ocaml.Js.string ev); + Eliom_comet.activate (); + Js_of_ocaml.Js._true)) + Js_of_ocaml.Js._false + in + activate "online"; activate "resume" + +(* Restart on a given URL *) + +let storage () = + Js_of_ocaml.Js.Optdef.case + Js_of_ocaml.Dom_html.window##.localStorage + (fun () -> failwith "Browser storage not supported") + (fun v -> v) + +let () = + let st = storage () in + let lc = Js_of_ocaml.Js.string "__os_restart_url" in + Js_of_ocaml.Js.Opt.case + (st##getItem lc) + (fun () -> ()) + (fun url -> + st##removeItem lc; + change_page_uri (Js_of_ocaml.Js.to_string url)) + +(* Handle universal links *) + +type event = + < url : Js_of_ocaml.Js.js_string Js_of_ocaml.Js.t Js_of_ocaml.Js.readonly_prop + ; scheme : + Js_of_ocaml.Js.js_string Js_of_ocaml.Js.t Js_of_ocaml.Js.readonly_prop + ; host : + Js_of_ocaml.Js.js_string Js_of_ocaml.Js.t Js_of_ocaml.Js.readonly_prop + ; path : + Js_of_ocaml.Js.js_string Js_of_ocaml.Js.t Js_of_ocaml.Js.readonly_prop + ; params : 'a. 'a Js_of_ocaml.Js.t Js_of_ocaml.Js.readonly_prop > + +let universal_links () = + let%lwt () = ondeviceready in + Lwt.return @@ Js_of_ocaml.Js.Optdef.to_option + @@ (Js_of_ocaml.Js.Unsafe.global##.universalLinks + : < subscribe : + Js_of_ocaml.Js.js_string Js_of_ocaml.Js.opt + -> (event Js_of_ocaml.Js.t -> unit) Js_of_ocaml.Js.callback + -> unit Js_of_ocaml.Js.meth + ; unsubscribe : + Js_of_ocaml.Js.js_string Js_of_ocaml.Js.opt + -> unit Js_of_ocaml.Js.meth > + Js_of_ocaml.Js.t + Js_of_ocaml.Js.Optdef.t) + +let _ = + match%lwt universal_links () with + | Some universal_links -> + Js_of_ocaml.Firebug.console##log + (Js_of_ocaml.Js.string "Universal links: registering"); + universal_links##subscribe Js_of_ocaml.Js.null + (Js_of_ocaml.Js.wrap_callback (fun (ev : event Js_of_ocaml.Js.t) -> + Js_of_ocaml.Firebug.console##log_2 + (Js_of_ocaml.Js.string "Universal links: got link") + ev##.url; + change_page_uri (Js_of_ocaml.Js.to_string ev##.url))); + Js_of_ocaml.Firebug.console##log + (Js_of_ocaml.Js.string "Universal links: registered"); + Lwt.return_unit + | None -> Lwt.return_unit + +(* Debugging *) + +(* Enable debugging messages. + + If you need to display debugging messages in the client side JS + debugger console, you can do so by uncommenting the following + lines. *) +(* let () = Eliom_config.debug_timings := true *) +(* let () = Lwt_log_core.add_rule "eliom:client*" Lwt_log_js.Debug *) +(* let () = Lwt_log_core.add_rule "os*" Lwt_log_js.Debug *) diff --git a/daegsrv/daegsrv_mobile.eliomi b/daegsrv/daegsrv_mobile.eliomi new file mode 100644 index 0000000..1733702 --- /dev/null +++ b/daegsrv/daegsrv_mobile.eliomi @@ -0,0 +1,2 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) diff --git a/daegsrv/daegsrv_page.eliom b/daegsrv/daegsrv_page.eliom new file mode 100644 index 0000000..59e4280 --- /dev/null +++ b/daegsrv/daegsrv_page.eliom @@ -0,0 +1,66 @@ +[%%shared +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +open Eliom_content.Html.F] + +[%%client +module Ocsigen_config = struct + let get_debugmode () = false +end] + +let%server css_name = !Daegsrv_config.css_name + +let%client css_name = + try Js_of_ocaml.Js.to_string Js_of_ocaml.Js.Unsafe.global##.___css_name_ + with _ -> "" + +let%server css_name_script = + [script (cdata_script (Printf.sprintf "var __css_name = '%s';" css_name))] + +let%client css_name_script = [] + +(* Warning: either we use exactly the same global node (and make sure + global nodes work properly on client side), or we do not add the + script on client side. We chose the second solution. *) +let%server app_js = + [Daegsrv_base.App.application_script ~defer:true ()] + +let%client app_js = [] +let%server the_local_js = [] +let%client the_local_js = [] (* in index.html *) +let%shared the_local_css = [[css_name]] + +[%%shared +module Page_config = struct + include Os_page.Default_config + + let title = "daegsrv" + let local_js = the_local_js + let local_css = the_local_css + + let other_head = + meta + ~a: + [ a_name "viewport" + ; a_content "width=device-width, initial-scale=1, user-scalable=no" ] + () + :: css_name_script + @ app_js + + let default_predicate _ _ = Lwt.return_true + let default_connected_predicate _ _ _ = Lwt.return_true + + let default_error_page _ _ exn = + Daegsrv_container.page None + (if Ocsigen_config.get_debugmode () + then [p [txt (Printexc.to_string exn)]] + else [p [txt "Error"]]) + + let default_connected_error_page myid_o _ _ exn = + Daegsrv_container.page myid_o + (if Ocsigen_config.get_debugmode () + then [p [txt (Printexc.to_string exn)]] + else [p [txt "Error"]]) +end + +include Os_page.Make (Page_config)] diff --git a/daegsrv/daegsrv_page.eliomi b/daegsrv/daegsrv_page.eliomi new file mode 100644 index 0000000..ca22f67 --- /dev/null +++ b/daegsrv/daegsrv_page.eliomi @@ -0,0 +1,63 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +[%%shared.start] + +val css_name : string +val css_name_script : [> Html_types.script] Eliom_content.Html.F.elt list +val app_js : [> `Script] Eliom_content.Html.elt list +val the_local_js : 'a list +val the_local_css : string list list + +module Page_config : sig + val js : string list list + val css : string list list + val title : string + val local_js : 'a list + val local_css : string list list + val other_head : [> Html_types.head_content] Eliom_content.Html.F.elt list + val default_predicate : 'a -> 'b -> bool Lwt.t + val default_connected_predicate : 'a -> 'b -> 'c -> bool Lwt.t + val default_error_page : 'a -> 'b -> exn -> Os_page.content Lwt.t + + val default_connected_error_page + : Os_types.User.id option + -> 'a + -> 'b + -> exn + -> Os_page.content Lwt.t +end + +val make_page : Os_page.content -> [> Html_types.html] Eliom_content.Html.elt + +val page + : ?predicate:('a -> 'b -> bool Lwt.t) + -> ?fallback:('a -> 'b -> exn -> Os_page.content Lwt.t) + -> ('a -> 'b -> Os_page.content Lwt.t) + -> 'a + -> 'b + -> Html_types.html Eliom_content.Html.elt Lwt.t + +module Opt : sig + val connected_page + : ?allow:Os_types.Group.t list + -> ?deny:Os_types.Group.t list + -> ?predicate:(Os_types.User.id option -> 'a -> 'b -> bool Lwt.t) + -> ?fallback: + (Os_types.User.id option -> 'a -> 'b -> exn -> Os_page.content Lwt.t) + -> (Os_types.User.id option -> 'a -> 'b -> Os_page.content Lwt.t) + -> 'a + -> 'b + -> Html_types.html Eliom_content.Html.elt Lwt.t +end + +val connected_page + : ?allow:Os_types.Group.t list + -> ?deny:Os_types.Group.t list + -> ?predicate:(Os_types.User.id option -> 'a -> 'b -> bool Lwt.t) + -> ?fallback: + (Os_types.User.id option -> 'a -> 'b -> exn -> Os_page.content Lwt.t) + -> (Os_types.User.id -> 'a -> 'b -> Os_page.content Lwt.t) + -> 'a + -> 'b + -> Html_types.html Eliom_content.Html.elt Lwt.t diff --git a/daegsrv/daegsrv_phone_connect.eliom b/daegsrv/daegsrv_phone_connect.eliom new file mode 100644 index 0000000..ea5da8c --- /dev/null +++ b/daegsrv/daegsrv_phone_connect.eliom @@ -0,0 +1,36 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +[%%shared.start] + +(* Edit this file to enable phone connectivity. + + [enable] has to be set to [true]. + + Use [Os_connect_phone.set_send_sms_handler] to register your + SMS-sending function, e.g., by using Amazon SNS or Twilio. + + You can remove this file if you don't need this functionality. *) + +let enable = false + +let%server () = + if enable + then + Os_connect_phone.set_send_sms_handler (fun ~number message -> + Printf.printf "Send SMS %s to %s\n%!" message number; + Lwt.return (Ok ())) + +let () = + if enable + then ( + Os_user_view.enable_phone (); + Eliom_registration.Action.register + ~service:Os_services.confirm_code_recovery_service + Os_handlers.confirm_code_recovery_handler; + Eliom_registration.Action.register + ~service:Os_services.confirm_code_extra_service + Os_handlers.confirm_code_extra_handler; + Eliom_registration.Action.register + ~service:Os_services.confirm_code_signup_service + Os_handlers.confirm_code_signup_handler) diff --git a/daegsrv/daegsrv_services.eliom b/daegsrv/daegsrv_services.eliom new file mode 100644 index 0000000..ac99401 --- /dev/null +++ b/daegsrv/daegsrv_services.eliom @@ -0,0 +1,40 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +let%server about_service = + Eliom_service.create + ~path:(Eliom_service.Path ["about"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +let%server upload_user_avatar_service : (unit, unit) Ot_picture_uploader.service + = + Ot_picture_uploader.mk_service "upload_user_avatar_service" [%json: unit] + +let%server demo_service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +let%server settings_service = + Eliom_service.create + ~path:(Eliom_service.Path ["settings"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +let%server os_github_service = + Eliom_service.extern ~prefix:"http://github.com" + ~path:["ocsigen"; "ocsigen-start"] + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +let%server ocsigen_service = + Eliom_service.extern ~prefix:"http://ocsigen.org" ~path:[] + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +let%client about_service = ~%about_service +let%client upload_user_avatar_service = ~%upload_user_avatar_service +let%client demo_service = ~%demo_service +let%client settings_service = ~%settings_service +let%client ocsigen_service = ~%ocsigen_service +let%client os_github_service = ~%os_github_service +(* The OS lib needs access to the settings service to perform + redirections to it. We need to register it *) +let%server () = Os_services.register_settings_service settings_service diff --git a/daegsrv/daegsrv_services.eliomi b/daegsrv/daegsrv_services.eliomi new file mode 100644 index 0000000..e87487d --- /dev/null +++ b/daegsrv/daegsrv_services.eliomi @@ -0,0 +1,76 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +[%%shared.start] + +val about_service + : ( unit + , unit + , Eliom_service.get + , Eliom_service.att + , Eliom_service.non_co + , Eliom_service.non_ext + , Eliom_service.reg + , [`WithoutSuffix] + , unit + , unit + , Eliom_service.non_ocaml ) + Eliom_service.t + +val upload_user_avatar_service : (unit, unit) Ot_picture_uploader.service + +val demo_service + : ( unit + , unit + , Eliom_service.get + , Eliom_service.att + , Eliom_service.non_co + , Eliom_service.non_ext + , Eliom_service.reg + , [`WithoutSuffix] + , unit + , unit + , Eliom_service.non_ocaml ) + Eliom_service.t + +val settings_service + : ( unit + , unit + , Eliom_service.get + , Eliom_service.att + , Eliom_service.non_co + , Eliom_service.non_ext + , Eliom_service.reg + , [`WithoutSuffix] + , unit + , unit + , Eliom_service.non_ocaml ) + Eliom_service.t + +val os_github_service + : ( unit + , unit + , Eliom_service.get + , Eliom_service.att + , Eliom_service.non_co + , Eliom_service.ext + , Eliom_service.non_reg + , [`WithoutSuffix] + , unit + , unit + , Eliom_service.non_ocaml ) + Eliom_service.t + +val ocsigen_service + : ( unit + , unit + , Eliom_service.get + , Eliom_service.att + , Eliom_service.non_co + , Eliom_service.ext + , Eliom_service.non_reg + , [`WithoutSuffix] + , unit + , unit + , Eliom_service.non_ocaml ) + Eliom_service.t diff --git a/daegsrv/daegsrv_settings.eliom b/daegsrv/daegsrv_settings.eliom new file mode 100644 index 0000000..556472c --- /dev/null +++ b/daegsrv/daegsrv_settings.eliom @@ -0,0 +1,158 @@ +[%%client +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +open Js_of_ocaml_lwt] + +let%shared update_main_email_button email = + let open Eliom_content.Html in + let button = + D.button + ~a:[D.a_class ["button"]] + [D.txt [%i18n S.set_as_main_email ~capitalize:true]] + in + ignore + [%client + (Lwt.async (fun () -> + Lwt_js_events.clicks (Eliom_content.Html.To_dom.of_element ~%button) + (fun _ _ -> + let%lwt () = Os_current_user.update_main_email ~%email in + Eliom_client.change_page + ~service:Daegsrv_services.settings_service () ())) + : unit)]; + button + +(* A button to remove the email from the database *) +let%shared delete_email_button email = + let open Eliom_content.Html in + let button = + D.button + ~a:[D.a_class ["button"; "os-remove-email-button"]] + [Daegsrv_icons.D.trash ()] + in + ignore + [%client + (Lwt.async (fun () -> + Lwt_js_events.clicks (Eliom_content.Html.To_dom.of_element ~%button) + (fun _ _ -> + let%lwt () = Os_current_user.remove_email_from_user ~%email in + Eliom_client.change_page + ~service:Daegsrv_services.settings_service () ())) + : unit)]; + button + +(* A list of buttons to update or to remove the email depending on the + email properties *) +let%shared buttons_of_email is_main_email is_validated email = + if is_main_email + then [] + else if is_validated + then [update_main_email_button email; delete_email_button email] + else [delete_email_button email] + +(* A list of labels describing the email properties. *) +let%shared labels_of_email is_main_email is_validated = + let open Eliom_content.Html.F in + let valid_label = + span + ~a:[a_class ["os-settings-label"; "os-validated-email"]] + [ (txt + @@ + if is_validated + then [%i18n S.validated ~capitalize:true] + else [%i18n S.waiting_confirmation ~capitalize:true]) ] + in + if is_main_email + then + [ span + ~a:[a_class ["os-settings-label"; "os-main-email"]] + [%i18n main_email ~capitalize:true] + ; valid_label ] + else [valid_label] + +let%shared li_of_email main_email (email, is_validated) = + let is_main_email = + match main_email with + | Some main_email -> main_email = email + | None -> false + in + let open Eliom_content.Html.D in + let labels = labels_of_email is_main_email is_validated + and buttons = buttons_of_email is_main_email is_validated email + and email = span ~a:[a_class ["os-settings-email"]] [txt email] in + Lwt.return (li ((email :: labels) @ buttons)) + +let%shared ul_of_emails (main_email, emails) = + let li_of_email = li_of_email main_email in + let%lwt li_list = Lwt_list.map_s li_of_email emails in + Lwt.return Eliom_content.Html.D.(div ~a:[a_class ["os-emails"]] [ul li_list]) + +(* List with information about emails *) +let%rpc get_emails myid () : (string option * (string * bool) list) Lwt.t = + let%lwt main_email = Os_db.User.email_of_userid myid in + let%lwt emails = Os_db.User.emails_of_userid myid in + let%lwt emails = + Lwt_list.map_s + (fun email -> + let%lwt v = Os_current_user.is_email_validated email in + Lwt.return (email, v)) + emails + in + Lwt.return (main_email, emails) + +let%shared select_language_form select_language_name = + let open Eliom_content.Html in + let current_language = Daegsrv_i18n.get_language () in + let all_languages_except_current = + List.filter + (fun l -> l <> current_language) + Daegsrv_i18n.languages + in + let form_option_of_language language is_current_language = + D.Form.Option + ( [] + , (* No attributes *) + Daegsrv_i18n.string_of_language language + , None + , is_current_language ) + in + [ D.p [D.txt [%i18n S.change_language]] + ; D.Form.select ~name:select_language_name D.Form.string + (form_option_of_language current_language true) + (List.map + (fun l -> form_option_of_language l false) + all_languages_except_current) + ; D.Form.input ~input_type:`Submit ~value:[%i18n S.send ~capitalize:true] + D.Form.string ] + +let%shared settings_content () = + let%lwt emails = get_emails () in + let%lwt emails = ul_of_emails emails in + Lwt.return + @@ Eliom_content.Html.D. + [ div + ~a:[a_class ["os-settings"]] + [ p [%i18n change_password ~capitalize:true] + ; Os_user_view.password_form ~a_placeholder_pwd:[%i18n S.password] + ~a_placeholder_confirmation:[%i18n S.retype_password] + ~text_send_button:[%i18n S.send] + ~service:Os_services.set_password_service () + ; br () + ; Os_user_view.upload_pic_link + ~submit:([a_class ["button"]], [txt "Submit"]) + ~content:[%i18n change_profile_picture] + Daegsrv_services.upload_user_avatar_service + ; br () + ; Os_user_view.reset_tips_link + ~text_link:[%i18n S.see_help_again_from_beginning] () + ; br () + ; Os_user_view.disconnect_all_link + ~text_link:[%i18n S.disconnect_all] () + ; br () + ; p [%i18n link_new_email] + ; Os_user_view.generic_email_form + ~a_placeholder_email:[%i18n S.email_address] ~text:[%i18n S.send] + ~service:Os_services.add_email_service () + ; p [%i18n currently_registered_emails] + ; div ~a:[a_class ["os-emails"]] [emails] + ; Form.post_form ~service:Os_services.update_language_service + select_language_form () ] ] diff --git a/daegsrv/demo.eliom b/daegsrv/demo.eliom new file mode 100644 index 0000000..f0b481a --- /dev/null +++ b/daegsrv/demo.eliom @@ -0,0 +1,43 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +[%%shared open Eliom_content.Html.D] + +(* drawer / demo welcome page ***********************************************) + +let%shared handler myid_o () () = + Daegsrv_container.page + ~a:[a_class ["os-page-demo"]] + myid_o + [ h2 [%i18n Demo.general_principles] + ; p [%i18n Demo.intro_1] + ; p [%i18n Demo.intro_2] + ; p [%i18n Demo.widget_ot] + ; p [%i18n Demo.widget_see_drawer] + ; p [%i18n Demo.widget_feel_free] + ; p [%i18n Demo.intro_3] ] + +let%shared () = + let registerDemo (module D : Demo_tools.Page) = + Daegsrv_base.App.register ~service:D.service + ( Daegsrv_page.Opt.connected_page @@ fun myid_o () () -> + let%lwt p = D.page () in + Daegsrv_container.page ~a:[a_class [D.page_class]] myid_o p ) + in + List.iter registerDemo Demo_tools.demos; + Daegsrv_base.App.register + ~service:Daegsrv_services.demo_service + (Daegsrv_page.Opt.connected_page handler) + +(* [detail_page_handler] is not registered in [Demo_tools] because we + - don't want to show detail pages in the menu. *) +let%shared () = + let detail_page_handler myid_o page () = + Daegsrv_container.page + ~a:[a_class ["os-page-demo-transition"]] + myid_o + (Demo_pagetransition.make_detail_page page ()) + in + Daegsrv_base.App.register + ~service:Demo_pagetransition.detail_page_service + (Daegsrv_page.Opt.connected_page detail_page_handler) diff --git a/daegsrv/demo_cache.eliom b/daegsrv/demo_cache.eliom new file mode 100644 index 0000000..01e1cac --- /dev/null +++ b/daegsrv/demo_cache.eliom @@ -0,0 +1,31 @@ +[%%shared +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* Eliom_cscache demo *) + +open Eliom_content.Html.F] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-cache"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.cache] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-cache" + +(* Page for this demo *) +let%shared page () = + Lwt.return + [ h1 [%i18n Demo.cache_1] + ; p + [%i18n + Demo.cache_2 + ~eliom_cscache:[code [txt "Eliom_cscache"]] + ~os_user_proxy:[code [txt "Os_user_proxy"]]] + ; p [%i18n Demo.cache_3 ~eliom_cscache:[code [txt "Eliom_cscache"]]] + ; p [%i18n Demo.cache_4 ~eliom_cscache:[code [txt "Eliom_cscache"]]] ] diff --git a/daegsrv/demo_calendar.eliom b/daegsrv/demo_calendar.eliom new file mode 100644 index 0000000..b1471b4 --- /dev/null +++ b/daegsrv/demo_calendar.eliom @@ -0,0 +1,64 @@ +[%%shared +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* Calendar demo *) + +open Eliom_content.Html.D] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-calendar"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* A reactive value containing the currently selected date *) +(* NOTE: in this example, we define a shared signal on the server side. Its + original value can only be read when the server generates the first page + (declaring it `%client`-only would obviously not work) and injected to be + read-/writable on the (possibly disconnected) client side since any + *shared value* is injectable; subsequent updates won't be sent to the server. + Declaring this signal as `%shared` wouldn't work either, as you'd end up with + two different signals (one for each side): a Reactive `map` in `page` would + use the server's signal when it's first generated on the server, while the + client-side click event would use its own `f`, so nothing would actually + happen. You can observe this duplication by replacing `%server` below with + `%shared`: the compiler will emit an error because the type of one of those + signals can't be inferred (it remains unknown at the end of the typing pass) + since it's never used throughout the program. *) +let%server s, f = Eliom_shared.React.S.create None + +let%client action y m d = + ~%f (Some (y, m, d)); + Lwt.return_unit + +let%shared string_of_date = function + | Some (y, m, d) -> + [%i18n + Demo.S.you_click_on_date ~y:(string_of_int y) ~m:(string_of_int m) + ~d:(string_of_int d)] + | None -> "" + +let%server date_as_string () : string Eliom_shared.React.S.t = + Eliom_shared.React.S.map [%shared string_of_date] s + +let%rpc date_reactive () : string Eliom_shared.React.S.t Lwt.t = + Lwt.return @@ date_as_string () + +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.calendar] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-calendar" + +(* Page for this demo *) +let%shared page () = + let calendar = + Ot_calendar.make ~click_non_highlighted:true ~action:[%client action] () + in + let%lwt dr = date_reactive () in + Lwt.return + [ h1 [%i18n Demo.calendar] + ; p [%i18n Demo.this_page_show_calendar] + ; div ~a:[a_class ["os-calendar"]] [calendar] + ; p [Eliom_content.Html.R.txt dr] ] diff --git a/daegsrv/demo_carousel1.eliom b/daegsrv/demo_carousel1.eliom new file mode 100644 index 0000000..a11bd7d --- /dev/null +++ b/daegsrv/demo_carousel1.eliom @@ -0,0 +1,73 @@ +[%%client +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* Carousel demo *) + +open Eliom_content.Html] + +[%%shared open Eliom_content.Html.F] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-carousel1"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.carousel_1] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-carousel1" + +(* Bind arrow keys *) +let%shared bind_keys change carousel = + ignore + [%client + (let arrow_thread = + (* Wait for the carousel to be in the page + (in the case the page is generated client side): *) + let%lwt () = Ot_nodeready.nodeready (To_dom.of_element ~%carousel) in + Ot_carousel.bind_arrow_keys ~change:~%change + Js_of_ocaml.Dom_html.document##.body + in + (* Do not forget to cancel the thread when we remove the carousel + (here, when we go to another page): *) + Eliom_client.onunload (fun () -> Lwt.cancel arrow_thread) + : unit)] + +(* Page for this demo *) +let%shared page () = + let make_page name = + div + ~a:[a_class ["demo-carousel1-page"; "demo-carousel1-page-" ^ name]] + [txt "Page "; txt name] + in + let carousel_change_signal = + [%client + (React.E.create () + : ([`Goto of int | `Next | `Prev] as 'a) React.E.t + * (?step:React.step -> 'a -> unit))] + in + let update = [%client fst ~%carousel_change_signal] in + let change = [%client fun a -> snd ~%carousel_change_signal ?step:None a] in + let carousel_pages = ["1"; "2"; "3"; "4"] in + let length = List.length carousel_pages in + let carousel_content = List.map make_page carousel_pages in + let {Ot_carousel.elt = carousel; pos; vis_elts} = + Ot_carousel.make ~update carousel_content + in + let bullets = Ot_carousel.bullets ~change ~pos ~length ~size:vis_elts () in + let prev = Ot_carousel.previous ~change ~pos [] in + let next = Ot_carousel.next ~change ~pos ~vis_elts ~length [] in + bind_keys change carousel; + Lwt.return + [ h1 [%i18n Demo.carousel_1] + ; p [%i18n Demo.ot_carousel_first_example_1] + ; p [%i18n Demo.ot_carousel_first_example_2] + ; p [%i18n Demo.ot_carousel_first_example_3] + ; p [%i18n Demo.ot_carousel_first_example_4] + ; div + ~a:[a_class ["demo-carousel1"]] + [div ~a:[a_class ["demo-carousel1-box"]] [carousel; prev; next; bullets]] + ] diff --git a/daegsrv/demo_carousel2.eliom b/daegsrv/demo_carousel2.eliom new file mode 100644 index 0000000..2143e22 --- /dev/null +++ b/daegsrv/demo_carousel2.eliom @@ -0,0 +1,95 @@ +[%%shared +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* Page with several tabs *) + +open Eliom_content.Html] + +[%%shared open Eliom_content.Html.F] + +let%shared lorem_ipsum = + [ p + [ txt + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Hanc ergo intuens debet institutum illud quasi signum absolvere. Animi enim quoque dolores percipiet omnibus partibus maiores quam corporis. Atque haec ita iustitiae propria sunt, ut sint virtutum reliquarum communia. Sed ad bona praeterita redeamus. Duarum enim vitarum nobis erunt instituta capienda. Nunc ita separantur, ut disiuncta sint, quo nihil potest esse perversius. Hoc est non dividere, sed frangere. Duo Reges: constructio interrete. Satis est ad hoc responsum." + ] + ; p + [ txt + "Traditur, inquit, ab Epicuro ratio neglegendi doloris. Quod quidem iam fit etiam in Academia. Quodcumque in mentem incideret, et quodcumque tamquam occurreret. Immo vero, inquit, ad beatissime vivendum parum est, ad beate vero satis. Re mihi non aeque satisfacit, et quidem locis pluribus." + ] + ; p + [ txt + "Amicitiam autem adhibendam esse censent, quia sit ex eo genere, quae prosunt. Hoc loco tenere se Triarius non potuit. Facile est hoc cernere in primis puerorum aetatulis. Sed in rebus apertissimis nimium longi sumus. Utrum igitur tibi litteram videor an totas paginas commovere? Quid de Platone aut de Democrito loquar?" + ] ] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-carousel2"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.carousel_2] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-carousel2" + +(* Page for this demo *) +let%shared page () = + let make_page name = + let c = if name = "1" then lorem_ipsum else [] in + div + ~a:[a_class ["demo-carousel2-page"; "demo-carousel2-page-" ^ name]] + (p [txt "Page "; txt name] :: c) + in + let make_tab name = [txt "Page "; txt name] in + let carousel_change_signal = + [%client + (React.E.create () + : ([`Goto of int | `Next | `Prev] as 'a) React.E.t + * (?step:React.step -> 'a -> unit))] + in + let update = [%client fst ~%carousel_change_signal] in + let change = [%client fun a -> snd ~%carousel_change_signal ?step:None a] in + let carousel_pages = ["1"; "2"; "3"; "4"] in + let carousel_content = List.map make_page carousel_pages in + let tab_content = List.map make_tab carousel_pages in + let tabs_r = ref (div []) in + let get_header_height = + [%client + fun () -> + let t = To_dom.of_element !(~%tabs_r) in + int_of_float (Ot_size.client_top t) + t##.offsetHeight] + in + (* We want a "full-height" carousel. See Ot_carousel documentation. *) + let {Ot_carousel.elt = carousel; pos; swipe_pos} = + Ot_carousel.make ~update ~full_height:(`Header get_header_height) + carousel_content + in + let ribbon = Ot_carousel.ribbon ~change ~pos ~cursor:swipe_pos tab_content in + let tabs = + (* ribbon container is necessary for shadow, + because position:sticky is not interpreted as relative + on browsers that do not support sticky. *) + D.div ~a:[a_class ["demo-carousel2-tabs"]] [ribbon] + in + tabs_r := ribbon; + (* We want the tabs to be always visible on top of the page. + To do that, we use position: sticky; + As this is not available in all browsers, we use a polyfill to + simulate this behaviour when not supported: + *) + ignore + [%client + (Lwt.async (fun () -> + Lwt.map ignore + (Ot_sticky.make_sticky ~ios_html_scroll_hack:true ~dir:`Top ~%tabs)) + : unit)]; + Lwt.return + [ h1 [%i18n Demo.carousel_2] + ; p [%i18n Demo.ot_carousel_second_example_1] + ; p [%i18n Demo.ot_carousel_second_example_2] + ; p [%i18n Demo.ot_carousel_second_example_3] + ; div + ~a:[a_class ["demo-carousel2"]] + [div ~a:[a_class ["demo-carousel2-box"]] [tabs; carousel]] ] diff --git a/daegsrv/demo_carousel3.eliom b/daegsrv/demo_carousel3.eliom new file mode 100644 index 0000000..3e413d7 --- /dev/null +++ b/daegsrv/demo_carousel3.eliom @@ -0,0 +1,88 @@ +[%%shared +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* Wheel demo *) + +open Eliom_content.Html] + +[%%shared open Eliom_content.Html.F] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-carousel3"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.carousel_wheel] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-carousel3" + +(* Page for this demo *) +let%shared page () = + let carousel_pages = + [ [%i18n Demo.S.monday] ^ " 1" + ; [%i18n Demo.S.tuesday] ^ " 1" + ; [%i18n Demo.S.wednesday] ^ " 1" + ; [%i18n Demo.S.thursday] ^ " 1" + ; [%i18n Demo.S.friday] ^ " 1" + ; [%i18n Demo.S.saturday] ^ " 1" + ; [%i18n Demo.S.sunday] ^ " 1" + ; [%i18n Demo.S.monday] ^ " 2" + ; [%i18n Demo.S.tuesday] ^ " 2" + ; [%i18n Demo.S.wednesday] ^ " 2" + ; [%i18n Demo.S.thursday] ^ " 2" + ; [%i18n Demo.S.friday] ^ " 2" + ; [%i18n Demo.S.saturday] ^ " 2" + ; [%i18n Demo.S.sunday] ^ " 2" + ; [%i18n Demo.S.monday] ^ " 3" + ; [%i18n Demo.S.tuesday] ^ " 3" + ; [%i18n Demo.S.wednesday] ^ " 3" + ; [%i18n Demo.S.thursday] ^ " 3" + ; [%i18n Demo.S.friday] ^ " 3" + ; [%i18n Demo.S.saturday] ^ " 3" + ; [%i18n Demo.S.sunday] ^ " 3" + ; [%i18n Demo.S.monday] ^ " 4" + ; [%i18n Demo.S.tuesday] ^ " 4" + ; [%i18n Demo.S.wednesday] ^ " 4" + ; [%i18n Demo.S.thursday] ^ " 4" + ; [%i18n Demo.S.friday] ^ " 4" + ; [%i18n Demo.S.saturday] ^ " 4" + ; [%i18n Demo.S.sunday] ^ " 4" + ; [%i18n Demo.S.monday] ^ " 5" + ; [%i18n Demo.S.tuesday] ^ " 5" + ; [%i18n Demo.S.wednesday] ^ " 5" + ; [%i18n Demo.S.thursday] ^ " 5" + ; [%i18n Demo.S.friday] ^ " 5" + ; [%i18n Demo.S.saturday] ^ " 5" + ; [%i18n Demo.S.sunday] ^ " 5" ] + in + let length = List.length carousel_pages in + let carousel_content = List.map (fun p -> D.div [txt p]) carousel_pages in + let carousel_change_signal = + [%client + (React.E.create () + : ([`Goto of int | `Next | `Prev] as 'a) React.E.t + * (?step:React.step -> 'a -> unit))] + in + let update = [%client fst ~%carousel_change_signal] in + let change = [%client fun a -> snd ~%carousel_change_signal ?step:None a] in + let carousel, pos, _swipe_pos = + Ot_carousel.wheel + ~a:[a_class ["demo-carousel3"]] + ~update ~vertical:true ~inertia:1. ~position:10 ~transition_duration:3. + ~face_size:25 carousel_content + in + Lwt.return + [ h1 [%i18n Demo.carousel_wheel] + ; p [%i18n Demo.carousel_third_example_1] + ; carousel + ; div + [ Ot_carousel.previous ~a:[a_class ["demo-prev"]] ~change ~pos [] + ; Ot_carousel.next + ~a:[a_class ["demo-next"]] + ~change ~pos + ~vis_elts:(Eliom_shared.React.S.const 1) + ~length [] ] ] diff --git a/daegsrv/demo_i18n.eliom b/daegsrv/demo_i18n.eliom new file mode 100644 index 0000000..308b54a --- /dev/null +++ b/daegsrv/demo_i18n.eliom @@ -0,0 +1,45 @@ +[%%shared +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* Ocsigen_i18n demo *) + +open Eliom_content.Html.F] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-i18n"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.internationalization ~capitalize:true] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-i18n" + +(* Page for this demo *) +let%shared page () = + (* Syntax [%i18n key] or [%i18n Module.key] inserts + the text corresponding to the key, in the language chosen by the user, + as a list of HTML elements. + Syntax [%i18n S.key] or [%i18n Module.S.key] inserts the text as a string. + It is possible to give parameters (here a boolean ~capitalize, or + a piece of HTML text ~f1 or ~f2). Have a look at file + assets/daegsrv_Demo_i18n.tsv + to see how to write the corresponding translations. + *) + Lwt.return + [ h1 [%i18n Demo.internationalization ~capitalize:true] + ; p [%i18n Demo.internationalization_1] + ; p + [%i18n + Demo.internationalization_2 + ~f1:[code [txt "assets/daegsrv_i18n.tsv"]] + ~f2:[code [txt "daegsrv_i18n.eliom"]]] + ; p [txt [%i18n Demo.S.internationalization_3]] + ; p + [%i18n + Demo.internationalization_4 + ~f:[code [txt "assets/daegsrv_Demo_i18n.tsv"]] + ~demo_prefix:[code [txt "demo_"]]] ] diff --git a/daegsrv/demo_links.eliom b/daegsrv/demo_links.eliom new file mode 100644 index 0000000..a44321b --- /dev/null +++ b/daegsrv/demo_links.eliom @@ -0,0 +1,72 @@ +[%%shared +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* Static files demo *) + +open Eliom_content.Html.F] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-static-files"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.links_and_static_files] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-links" + +(* An example of external service: *) +let%server ocsigen_service = + Eliom_service.extern ~prefix:"http://ocsigen.org" ~path:[] + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client ocsigen_service = ~%ocsigen_service + +(* Page for this demo *) +let%shared page () = + Lwt.return + [ h1 [%i18n Demo.links_and_static_files] + ; h2 [%i18n Demo.services] + ; p + [%i18n + Demo.services_1 + ~f1:[code [txt "daegsrv_services.eliom"]] + ~f2:[code [txt "daegsrv_handlers.eliom"]] + ~f3:[code [txt "daegsrv.eliom"]]] + ; h2 [%i18n Demo.links_and_forms] + ; p + [%i18n + Demo.links_and_forms_1 + ~t1: + [a ~service:Os_services.main_service [%i18n Demo.internal_link] ()] + ~t2:[a ~service:ocsigen_service [%i18n Demo.external_service] ()]] + ; h2 [%i18n Demo.static_files] + ; p + [%i18n + Demo.static_files_1 + ~static:[code [txt "static"]] + ~static_dir:[code [txt "static_dir"]]] + ; img + ~a:[a_class ["demo-static-img"]] + ~alt:"local_img" + ~src: + (Eliom_content.Html.F.make_uri + ~absolute:false (* We want local file on mobile app *) + ~service:(Eliom_service.static_dir ()) + ["images"; "ocsigen.png"]) + () + ; p [%i18n Demo.static_files_2] + ; img + ~a:[a_class ["demo-static-img"]] + ~alt:"distant_img" + ~src: + (Eliom_content.Html.F.make_uri + (* We want a distant file: + keep the default value of ~absolute *) + ~service:(Eliom_service.static_dir ()) + ["images"; "ocsigen.png"]) + () ] diff --git a/daegsrv/demo_notif.eliom b/daegsrv/demo_notif.eliom new file mode 100644 index 0000000..6d80452 --- /dev/null +++ b/daegsrv/demo_notif.eliom @@ -0,0 +1,106 @@ +[%%client +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* Notification demo *) + +open Js_of_ocaml_lwt] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-notif"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.notification] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-notif" + +(* Instantiate function Os_notif.Simple for each kind of notification + you need. + The key is the resource ID. For example, if you are implementing a + messaging application, it can be the chatroom ID + (for example type key = int64). +*) +module Notif = Os_notif.Make_Simple (struct + type key = unit + + (* The resources identifiers. + Here unit because we have only one resource. *) + + type notification = string +end) + +(* Broadcast message [v] *) +let%rpc notify (v : string) : unit Lwt.t = + (* Notify all client processes listening on this resource + (identified by its key, given as first parameter) + by sending them message v. *) + Notif.notify (* ~notfor:`Me *) (() : Notif.key) v; + (* Use ~notfor:`Me to avoid receiving the message in this tab, + or ~notfor:(`User myid) to avoid sending to the current user. + (Where myid is Os_current_user.get_current_userid ()) + *) + Lwt.return_unit + +let%rpc listen () : unit Lwt.t = Notif.listen (); Lwt.return_unit + +(* Display a message every time the React event [e = Notif.client_ev ()] + happens. *) +let%server () = + Os_session.on_start_process (fun _ -> + let e : (unit * string) Eliom_react.Down.t = Notif.client_ev () in + ignore + [%client + (ignore + @@ React.E.map + (fun (_, msg) -> + (* Eliom_lib.alert "%s" msg *) + Os_msg.msg ~level:`Msg (Printf.sprintf "%s" msg)) + ~%e + : unit)]; + Lwt.return_unit) + +(* Make a text input field that calls [f s] for each [s] submitted *) +let%shared make_form msg f = + let inp = Eliom_content.Html.D.Raw.input () + and btn = + Eliom_content.Html.(D.button ~a:[D.a_class ["button"]] [D.txt msg]) + in + ignore + [%client + (Lwt.async @@ fun () -> + let btn = Eliom_content.Html.To_dom.of_element ~%btn + and inp = Eliom_content.Html.To_dom.of_input ~%inp in + Lwt_js_events.clicks btn @@ fun _ _ -> + let v = Js_of_ocaml.Js.to_string inp##.value in + let%lwt () = ~%f v in + inp##.value := Js_of_ocaml.Js.string ""; + Lwt.return_unit + : unit)]; + Eliom_content.Html.D.div [inp; btn] + +let%rpc unlisten () : unit Lwt.t = Notif.unlisten (); Lwt.return_unit + +(* Page for this demo *) +let%shared page () = + (* Subscribe to notifications when entering this page: *) + let%lwt () = listen () in + (* Unsubscribe from notifications when user leaves this page *) + let (_ : unit Eliom_client_value.t) = + [%client Eliom_client.Page_status.ondead (fun () -> Lwt.async unlisten)] + in + Lwt.return + Eliom_content.Html.F. + [ h1 [%i18n Demo.notification] + ; p + ([%i18n + Demo.exchange_msg_between_users ~os_notif:[code [txt "Os_notif"]]] + @ [ br () + ; txt [%i18n Demo.S.open_multiple_tabs_browsers] + ; br () + ; txt [%i18n Demo.S.fill_input_form_send_message] ]) + ; make_form [%i18n Demo.S.send_message] + [%client (notify : string -> unit Lwt.t)] ] diff --git a/daegsrv/demo_pagetransition.eliom b/daegsrv/demo_pagetransition.eliom new file mode 100644 index 0000000..cc4a3a0 --- /dev/null +++ b/daegsrv/demo_pagetransition.eliom @@ -0,0 +1,93 @@ +[%%shared +(* This demo illustrates Eliom's DOM caching feature. + + By running [Eliom_client.onload Eliom_client.push_history_dom] one + can push the DOM of the current page into Eliom's cache. Every page + which is cached in this manner will be immediately served from the + cache instead of being charged from the server or regenerated by + the client. Also the scroll position is restored that the page had + at the end of the last visit. *) +open Eliom_content] + +[%%shared open Html] +[%%shared open Html.D] +[%%client open Js_of_ocaml_lwt] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-page-transition"; ""]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +let%server detail_page_service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-page-transition"; "detail"; ""]) + ~meth:(Eliom_service.Get (Eliom_parameter.int "page")) + () + +(* Make service available on the client *) +let%client service = ~%service +let%client detail_page_service = ~%detail_page_service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.pagetransition] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-transition" + +let%shared create_item index = + let open F in + li + ~a: + [ a_class + ["demo-list-item"; Printf.sprintf "demo-list-item-%d" (index mod 5)] + ] + [a ~service:detail_page_service [txt (Printf.sprintf "list%d" index)] index] + +let%shared page () = + let l = + (fun i -> create_item (i + 1)) + |> Array.init 10 |> Array.to_list + |> ul ~a:[a_class ["demo-list"]] + in + let add_button = + div ~a:[a_class ["demo-button"]] [%i18n Demo.pagetransition_add_button] + in + ignore + [%client + ((* It is the address of the dom that will be stored in cache, so + it doesn't matter when [push_history_dom] is called. However, + it is important that the dom is bound to the right state id. + So it is better to call [push_history_dom] in Eliom_client.onload, + when the state id has already been updated and the dom of + the current page is ready. *) + Eliom_client.onload Eliom_client.push_history_dom; + let counter = + let r = ref 10 in + fun () -> + r := !r + 1; + !r + in + Lwt_js_events.clicks (To_dom.of_element ~%add_button) (fun _ _ -> + Html.Manip.appendChild ~%l (create_item (counter ())); + Lwt.return_unit) + : unit Lwt.t)]; + Lwt.return + [ h1 [%i18n Demo.pagetransition_list_page] + ; p [%i18n Demo.pagetransition_intro] + ; l + ; add_button ] + +let%shared make_detail_page page () = + let back_button = + div ~a:[a_class ["demo-button"]] [%i18n Demo.pagetransition_back_button] + in + ignore + [%client + (Lwt.async (fun () -> + Lwt_js_events.clicks (To_dom.of_element ~%back_button) (fun _ _ -> + Js_of_ocaml.Dom_html.window##.history##back; + Lwt.return_unit)) + : unit)]; + [ h1 + ([%i18n Demo.pagetransition_detail_page] + @ [txt (Printf.sprintf " %d" page)]) + ; back_button ] diff --git a/daegsrv/demo_pgocaml.eliom b/daegsrv/demo_pgocaml.eliom new file mode 100644 index 0000000..b1c6e4a --- /dev/null +++ b/daegsrv/demo_pgocaml.eliom @@ -0,0 +1,46 @@ +[%%shared +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* PGOcaml demo *) + +open Eliom_content.Html.F] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-pgocaml"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.pgocaml] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-pgocaml" + +(* Fetch users in database *) +let%rpc get_users () : string list Lwt.t = + (* For this demo, we add a delay to simulate a network or db latency: *) + let%lwt () = Lwt_unix.sleep 2. in + Demo_pgocaml_db.get () + +(* Generate page for this demo *) +let%shared page () = + let%lwt user_block = + Ot_spinner.with_spinner + (let%lwt users = get_users () in + let users = + List.map + (fun u -> if u = "" then li [em [txt "new user"]] else li [txt u]) + users + in + if users = [] + then Lwt.return [p [em [%i18n Demo.no_user_create_accounts]]] + else Lwt.return [p [%i18n Demo.pgocaml_users]; ul users]) + in + Lwt.return + [ h1 [%i18n Demo.pgocaml] + ; p [%i18n Demo.pgocaml_description_1] + ; p [%i18n Demo.pgocaml_description_2] + ; p [%i18n Demo.pgocaml_description_3] + ; user_block ] diff --git a/daegsrv/demo_pgocaml_db.ml b/daegsrv/demo_pgocaml_db.ml new file mode 100644 index 0000000..4d0b396 --- /dev/null +++ b/daegsrv/demo_pgocaml_db.ml @@ -0,0 +1,13 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +open Os_db + +(* We are using PGOCaml to make type safe DB requests to Postgresql. + The Makefile automatically compiles + all files *_db.ml with PGOCaml's ppx syntax extension. +*) + +let get () = + full_transaction_block (fun dbh -> + [%pgsql dbh "SELECT lastname FROM ocsigen_start.users"]) diff --git a/daegsrv/demo_popup.eliom b/daegsrv/demo_popup.eliom new file mode 100644 index 0000000..4cfeb73 --- /dev/null +++ b/daegsrv/demo_popup.eliom @@ -0,0 +1,68 @@ +[%%shared +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* Popup button demo *) + +open Eliom_content.Html] + +[%%shared open Eliom_content.Html.F] +[%%client open Js_of_ocaml_lwt] + +(* Service for this demo, defined in the server-side app *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-popup"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu. This value is defined both server and client-side. *) +let%shared name () = [%i18n Demo.S.popup] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-popup" + +(* The function generating the page can be called either from the server or + the client (shared section). *) +let%shared page () = + let button = + (* As we are using ~%button (in a client section below) + to refer to this precise occurrence of the button in the page, + button must be a D node + (from module Eliom_content.Html.D, + which will add an unique identifier in its attributes), + and not a functional node (Eliom_content.Html.F). *) + D.Form.input + ~a:[a_class ["button"]] + ~input_type:`Submit ~value:[%i18n Demo.S.popup_click] Form.string + in + (* Every time this page is generated, + we want to execute the following piece of client-side code. + Lwt_js_events.clicks means "For each click on ... do ...". + It creates an Lwt thread that never returns. + We run it asynchronously using Lwt.async. + Lwt_js_events.clicks is expecting a DOM node + (i.e. an actual part of the current page). + To_dom.of_element will return the DOM node corresponding to the + OCaml value ~%button. + ~%button refers to the value button, defined outside [%client ] section + (possibly on server or client). + *) + ignore + [%client + (* This client section will be executed after the page is + displayed by the browser. *) + (Lwt.async (fun () -> + (* Lwt_js_events.clicks returns a Lwt thread, which never terminates. + We run it asynchronously. *) + Lwt_js_events.clicks (To_dom.of_element ~%button) (fun _ _ -> + let%lwt _ = + Ot_popup.popup ~close_button:[Os_icons.F.close ()] (fun _ -> + Lwt.return @@ p [%i18n Demo.popup_message]) + in + Lwt.return_unit)) + : unit)]; + (* Page elements, using module Eliom_content.Html.F + (as we don't want to add a unique identifier). + See internationalization demo for i18n syntax. + *) + Lwt.return [h1 [%i18n Demo.popup]; p [%i18n Demo.popup_content]; p [button]] diff --git a/daegsrv/demo_pulltorefresh.eliom b/daegsrv/demo_pulltorefresh.eliom new file mode 100644 index 0000000..a65e9e0 --- /dev/null +++ b/daegsrv/demo_pulltorefresh.eliom @@ -0,0 +1,47 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +(** Demo for refreshable content *) + +[%%shared open Eliom_content.Html] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-pull-to-refresh"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.pull_to_refresh] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-pull" + +let%shared page () = + let counter_sig, set_counter = Eliom_shared.React.S.create 0 in + let reload = + [%client + fun () -> + let%lwt _ = Js_of_ocaml_lwt.Lwt_js.sleep 1. in + let n = Eliom_shared.React.S.value ~%counter_sig in + ~%set_counter (n + 1); + Lwt.return_true] + in + let counter_node_sig = + Eliom_shared.React.S.map + [%shared + fun n -> + let n = [F.txt @@ string_of_int n] in + F.p [%i18n Demo.pull_to_refresh_counter ~n]] + counter_sig + in + let content = + F.div + ~a:[F.a_class ["demo-pull-to-refresh-content"]] + [ F.h1 [%i18n Demo.pull_to_refresh] + ; F.p [%i18n Demo.pull_to_refresh_1] + ; F.p [%i18n Demo.pull_to_refresh_2] + ; R.node counter_node_sig ] + in + Lwt.return @@ [Ot_pulltorefresh.make ~dragThreshold:15. ~content reload] diff --git a/daegsrv/demo_react.eliom b/daegsrv/demo_react.eliom new file mode 100644 index 0000000..38ed9a5 --- /dev/null +++ b/daegsrv/demo_react.eliom @@ -0,0 +1,72 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +(** Demo for shared reactive content *) + +[%%client open Js_of_ocaml_lwt] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-react"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.reactive_programming] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-react" + +(* Make a text input field that calls [f s] for each [s] submitted *) +let%shared make_form msg f = + let inp = Eliom_content.Html.D.Raw.input () + and btn = + Eliom_content.Html.(D.button ~a:[D.a_class ["button"]] [D.txt msg]) + in + ignore + [%client + (Lwt.async @@ fun () -> + let btn = Eliom_content.Html.To_dom.of_element ~%btn + and inp = Eliom_content.Html.To_dom.of_input ~%inp in + Lwt_js_events.clicks btn @@ fun _ _ -> + let v = Js_of_ocaml.Js.to_string inp##.value in + let%lwt () = ~%f v in + inp##.value := Js_of_ocaml.Js.string ""; + Lwt.return_unit + : unit)]; + Eliom_content.Html.D.div [inp; btn] + +(* Page for this demo *) +let%shared page () = + (* Client reactive list, initially empty. + It can be defined either from client or server side, + (depending on whether this code is executed client or server-side). + Use Eliom_shared.ReactiveData.RList for lists or + Eliom_shared.React.S for other data types. + *) + let l, h = Eliom_shared.ReactiveData.RList.create [] in + let inp = + (* Form that performs a cons (client-side). *) + make_form [%i18n Demo.S.reactive_programming_button] + [%client + (fun v -> Lwt.return (Eliom_shared.ReactiveData.RList.cons v ~%h) + : string -> unit Lwt.t)] + and l = + (* Produce
  • items from l contents. + The shared function will first be called once server or client-side + to compute the initial page. It will then be called client-side + every time the reactive list changes to update the + page automatically. *) + Eliom_shared.ReactiveData.RList.map + [%shared (fun s -> Eliom_content.Html.(D.li [D.txt s]) : _ -> _)] + l + in + Lwt.return + Eliom_content.Html. + [ F.h1 [%i18n Demo.reactive_programming] + ; F.p [F.txt [%i18n Demo.S.reactive_programming_1]] + ; F.p [F.txt [%i18n Demo.S.reactive_programming_2]] + ; F.p [F.txt [%i18n Demo.S.reactive_programming_3]] + ; inp + ; F.div [R.ul l] ] diff --git a/daegsrv/demo_ref.eliom b/daegsrv/demo_ref.eliom new file mode 100644 index 0000000..e075975 --- /dev/null +++ b/daegsrv/demo_ref.eliom @@ -0,0 +1,55 @@ +[%%shared +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* Demo for Eliom references and Os_date *) + +open Eliom_content.Html.F] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-ref"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.eliom_ref] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-ref" + +(* An Eliom reference storing the last time the user visited the current + page. It has scope Eliom_common.default_group_scope, which means that + the value will be different for each user of the Web site, but the same + for all the sessions of a same user. + Ocsigen Start is creating a session group for each user. +*) +let%server last_visit = + Eliom_reference.eref ~persistent:"demo_last_visit" + ~scope:Eliom_common.default_group_scope None + +(* Read & reset last_visit *) +let%rpc get_reset_last_visit () : Os_date.local_calendar option Lwt.t = + let%lwt v = Eliom_reference.get last_visit in + let%lwt () = Eliom_reference.set last_visit (Some (Os_date.now ())) in + Lwt.return v + +(* Call get_reset_last_visit and produce pretty message *) +let%shared get_reset_last_visit_message () = + let%lwt last_visit = get_reset_last_visit () in + match last_visit with + | None -> Lwt.return [%i18n Demo.eliom_ref_first_visit] + | Some last_visit -> + Lwt.return + ([%i18n Demo.eliom_ref_last_visit] + @ [txt " "; txt (Os_date.smart_time last_visit)]) + +(* Generate page for this demo *) +let%shared page () = + let%lwt last_visit_message = get_reset_last_visit_message () in + Lwt.return + [ h1 [%i18n Demo.eliom_ref] + ; p [txt [%i18n Demo.S.eliom_ref_1]] + ; p [txt [%i18n Demo.S.eliom_ref_2]] + ; p last_visit_message + ; p [txt [%i18n Demo.S.eliom_ref_3]] ] diff --git a/daegsrv/demo_rpc.eliom b/daegsrv/demo_rpc.eliom new file mode 100644 index 0000000..1ece9cc --- /dev/null +++ b/daegsrv/demo_rpc.eliom @@ -0,0 +1,63 @@ +[%%client +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* RPC button demo *) + +open Js_of_ocaml_lwt] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-rpc"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.rpc_button] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-rpc" + +(* A server-side reference that stores data for the current browser + (scope = session). + It's also possible to define Eliom references with other scopes, + like client-process (a tab of a browser) or session-group (a user). + *) +let%server my_ref = + Eliom_reference.eref ~scope:Eliom_common.default_session_scope 0 + +(* Server-side function that increments my_ref and returns new val *) +let%rpc incr_my_ref () : int Lwt.t = + let%lwt v = Eliom_reference.get my_ref in + let v = v + 1 in + let%lwt () = Eliom_reference.set my_ref v in + Lwt.return v + +let%shared button msg f = + let btn = + Eliom_content.Html.(D.button ~a:[D.a_class ["button"]] [D.txt msg]) + in + ignore + [%client + (Lwt.async @@ fun () -> + Lwt_js_events.clicks (Eliom_content.Html.To_dom.of_element ~%btn) + (fun _ _ -> ~%f ()) + : unit)]; + btn + +(* Page for this demo *) +let%shared page () = + let btn = + button [%i18n Demo.S.rpc_button_click_increase] + [%client + (fun () -> + let%lwt v = incr_my_ref () in + Eliom_lib.alert "Update: %d" v; + Lwt.return_unit + : unit -> unit Lwt.t)] + in + Lwt.return + Eliom_content.Html. + [ F.h1 [%i18n Demo.rpc_button] + ; F.p [F.txt [%i18n Demo.S.rpc_button_description]] + ; F.p [btn] ] diff --git a/daegsrv/demo_spinner.eliom b/daegsrv/demo_spinner.eliom new file mode 100644 index 0000000..2d6071d --- /dev/null +++ b/daegsrv/demo_spinner.eliom @@ -0,0 +1,44 @@ +[%%client +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* Spinner demo *) + +open Js_of_ocaml_lwt] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-spinner"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.spinner] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-spinner" + +(* Build the spinner *) +let%client make_spinner () = + (* [Ot_spinner.with_spinner_no_lwt] accepts an Lwt thread "slowly" + producing HTML content *) + Ot_spinner.with_spinner_no_lwt + (* sleep for 5 seconds to simulate a delay, then return content *) + (let%lwt () = Lwt_js.sleep 5. in + Lwt.return + Eliom_content.Html.D. + [ txt [%i18n Demo.S.spinner_content_ready] + ; txt " " + ; txt [%i18n Demo.S.spinner_message_replace_spinner] ]) + +(* Page for this demo *) +let%shared page () = + Lwt.return + Eliom_content.Html. + [ F.h1 [%i18n Demo.spinner] + ; F.p [F.txt [%i18n Demo.S.spinner_description_ot]] + ; F.p [F.txt [%i18n Demo.S.spinner_description_1]] + ; F.p [F.txt [%i18n Demo.S.spinner_description_2]] + ; F.p [F.txt [%i18n Demo.S.spinner_description_3]] + ; F.p [F.txt [%i18n Demo.S.spinner_generated_client_side]] + ; C.node [%client (make_spinner () : [> `Div] Eliom_content.Html.elt)] ] diff --git a/daegsrv/demo_timepicker.eliom b/daegsrv/demo_timepicker.eliom new file mode 100644 index 0000000..397dc5e --- /dev/null +++ b/daegsrv/demo_timepicker.eliom @@ -0,0 +1,60 @@ +[%%shared +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +open Eliom_content.Html.D] + +[%%client open Js_of_ocaml_lwt] + +(* Timepicker demo *) + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-timepicker"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +let%server s, f = Eliom_shared.React.S.create None + +let%client action (h, m) = + ~%f (Some (h, m)); + Lwt.return_unit + +let%shared string_of_time = function + | Some (h, m) -> + [%i18n Demo.S.you_click_on_time ~h:(string_of_int h) ~m:(string_of_int m)] + | None -> "" + +let%server time_as_string () : string Eliom_shared.React.S.t = + Eliom_shared.React.S.map [%shared string_of_time] s + +let%rpc time_reactive () : string Eliom_shared.React.S.t Lwt.t = + Lwt.return @@ time_as_string () + +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.timepicker] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-timepicker" + +(* Page for this demo *) +let%shared page () = + let time_picker, _, back_f = + Ot_time_picker.make ~h24:true ~action:[%client action] () + in + let button = + Eliom_content.Html.D.button [%i18n Demo.timepicker_back_to_hours] + in + ignore + [%client + (Lwt.async (fun () -> + Lwt_js_events.clicks (Eliom_content.Html.To_dom.of_element ~%button) + (fun _ _ -> ~%back_f (); Lwt.return_unit)) + : _)]; + let%lwt tr = time_reactive () in + Lwt.return + [ h1 [%i18n Demo.timepicker] + ; p [%i18n Demo.timepicker_description] + ; div [time_picker] + ; p [Eliom_content.Html.R.txt tr] + ; div [button] ] diff --git a/daegsrv/demo_tips.eliom b/daegsrv/demo_tips.eliom new file mode 100644 index 0000000..fa613b1 --- /dev/null +++ b/daegsrv/demo_tips.eliom @@ -0,0 +1,48 @@ +[%%shared +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* Os_tips demo *) + +open Eliom_content.Html.F] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-tips"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.tips] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-tips" + +(* Here is an example of tip. Call this function while generating the + widget concerned by the explanation it contains. *) +let%shared example_tip () = + (* Have a look at the API documentation of module Os_tips for + more options. *) + Os_tips.bubble () ~top:[%client 40] ~right:[%client 0] ~width:[%client 300] + ~height:[%client 180] ~arrow:[%client `top 250] ~name:"example" + ~content: + [%client + fun _ -> + Lwt.return + Eliom_content.Html.F. + [p [%i18n Demo.example_tip]; p [%i18n Demo.look_module_tip]]] + +(* Page for this demo *) +let%shared page () = + (* Call the function defining the tip from the server or the client: *) + let%lwt () = example_tip () in + Lwt.return + [ h1 [%i18n Demo.tips1] + ; p [%i18n Demo.tips2 ~os_tips:[code [txt "Os_tips"]]] + ; p [%i18n Demo.tips3] + ; p + [%i18n + Demo.tips4 + ~set_page: + [ a ~service:Daegsrv_services.settings_service + [%i18n Demo.tips5] () ]] ] diff --git a/daegsrv/demo_tongue.eliom b/daegsrv/demo_tongue.eliom new file mode 100644 index 0000000..27c407c --- /dev/null +++ b/daegsrv/demo_tongue.eliom @@ -0,0 +1,39 @@ +[%%shared +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* Tongue demo *) + +open Eliom_content.Html.F] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-tongue"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.tongue_1] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-tongue" + +(* Page for this demo *) +let%shared page () = + let content = + [ div ~a:[a_class ["demo-tongue-1"]] [] + ; div ~a:[a_class ["demo-tongue-2"]] [] + ; div ~a:[a_class ["demo-tongue-3"]] [] + ; div ~a:[a_class ["demo-tongue-4"]] [] + ; div ~a:[a_class ["demo-tongue-5"]] [] + ; div ~a:[a_class ["demo-tongue-6"]] [] ] + in + let tongue = + Ot_tongue.tongue ~side:`Bottom + ~stops:[`Px 70; `Interval (`Percent 100, `Full_content)] + ~init:(`Px 70) content + in + Lwt.return + [ h1 [%i18n Demo.tongue_1] + ; p [%i18n Demo.ot_tongue_1] + ; div ~a:[a_class ["demo-tongue"]] [tongue.Ot_tongue.elt] ] diff --git a/daegsrv/demo_tools.eliom b/daegsrv/demo_tools.eliom new file mode 100644 index 0000000..c55b0e2 --- /dev/null +++ b/daegsrv/demo_tools.eliom @@ -0,0 +1,61 @@ +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +[%%shared.start] + +module type Page = sig + val name : unit -> string + val page_class : string + + val service + : ( unit + , unit + , Eliom_service.get + , Eliom_service.att + , Eliom_service.non_co + , Eliom_service.non_ext + , Eliom_service.reg + , [`WithoutSuffix] + , unit + , unit + , Eliom_service.non_ocaml ) + Eliom_service.t + + val page : unit -> Html_types.div_content Eliom_content.Html.D.elt list Lwt.t +end + +let demos = + [ (module Demo_popup : Page) + ; (module Demo_rpc) + ; (module Demo_ref) + ; (module Demo_spinner) + ; (module Demo_pgocaml) + ; (module Demo_users) + ; (module Demo_links) + ; (module Demo_i18n) + ; (module Demo_tips) + ; (module Demo_carousel1) + ; (module Demo_carousel2) + ; (module Demo_carousel3) + ; (module Demo_tongue) + ; (module Demo_calendar) + ; (module Demo_timepicker) + ; (module Demo_notif) + ; (module Demo_react) + ; (module Demo_pulltorefresh) + ; (module Demo_cache) + ; (module Demo_pagetransition) ] + +let drawer_contents () = + let open Eliom_content.Html.F in + let make_link (module D : Page) = + li [a ~service:D.service [txt @@ D.name ()] ()] + in + let submenu = + ul ~a:[a_class ["os-drawer-submenu"]] (List.map make_link demos) + in + li + [ a + ~a:[a_class ["os-drawer-item"]] + ~service:Daegsrv_services.demo_service [%i18n Demo.intro] () + ; submenu ] diff --git a/daegsrv/demo_users.eliom b/daegsrv/demo_users.eliom new file mode 100644 index 0000000..fd04eba --- /dev/null +++ b/daegsrv/demo_users.eliom @@ -0,0 +1,57 @@ +[%%shared +(* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. *) +(* Os_current_user demo *) + +open Eliom_content.Html.F] + +(* Service for this demo *) +let%server service = + Eliom_service.create + ~path:(Eliom_service.Path ["demo-users"]) + ~meth:(Eliom_service.Get Eliom_parameter.unit) () + +(* Make service available on the client *) +let%client service = ~%service +(* Name for demo menu *) +let%shared name () = [%i18n Demo.S.users] +(* Class for the page containing this demo (for internal use) *) +let%shared page_class = "os-page-demo-users" + +let%shared display_user_name = function + | None -> p [%i18n Demo.you_are_not_connected] + | Some user -> + p + [ txt ([%i18n Demo.S.you_are] ^ " ") + ; em [txt (Os_user.fullname_of_user user)] ] + +let%shared display_user_id = function + | None -> p [%i18n Demo.log_in_to_see_demo] + | Some userid -> + p + [ txt ([%i18n Demo.S.your_user_id] ^ " ") + ; em [txt (Int64.to_string userid)] ] + +(* Page for this demo *) +let%shared page () = + (* We use the convention to use "myid" for the user id of currently + connected user, and "userid" for all other user id. + We recommend to follow this convention, to reduce the risk + of mistaking an user for another. + We use prefix "_o" for optional value. + *) + let myid_o = Os_current_user.Opt.get_current_userid () in + let me_o = Os_current_user.Opt.get_current_user () in + Lwt.return + [ h1 [%i18n Demo.users] + ; p + [ txt [%i18n Demo.S.the_module] + ; code [txt " Os_current_user "] + ; txt [%i18n Demo.S.allows_get_information_currently_connected_user] ] + ; display_user_name me_o + ; display_user_id myid_o + ; p [txt [%i18n Demo.S.these_functions_called_server_or_client_side]] + ; p + [ txt [%i18n Demo.S.always_get_current_user_using_module] + ; code [txt " Os_current_user. "] + ; txt [%i18n Demo.S.never_trust_client_pending_user_id] ] ] diff --git a/daegsrv/mobile/.chcpignore b/daegsrv/mobile/.chcpignore new file mode 100644 index 0000000..e69de29 diff --git a/daegsrv/mobile/chcp.json.in b/daegsrv/mobile/chcp.json.in new file mode 100644 index 0000000..fb502a1 --- /dev/null +++ b/daegsrv/mobile/chcp.json.in @@ -0,0 +1,5 @@ +{ + "release": "%%DATE%%", + "content_url": "%%APPSERVER%%%%APPPATH%%/update/%%DATE%%", + "update": "now" +} diff --git a/daegsrv/mobile/config.xml.in b/daegsrv/mobile/config.xml.in new file mode 100644 index 0000000..0e64ccb --- /dev/null +++ b/daegsrv/mobile/config.xml.in @@ -0,0 +1,165 @@ + + + %%MOBILE_USE_CLEARTEXT_TRAFFIC%% + %%MOBILE_APP_NAME%% + + %%MOBILE_DESCRIPTION%% + + + %%MOBILE_AUTHOR_DESCRIPTION%% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/daegsrv/mobile/eliom.html.in b/daegsrv/mobile/eliom.html.in new file mode 100644 index 0000000..22d26bf --- /dev/null +++ b/daegsrv/mobile/eliom.html.in @@ -0,0 +1,38 @@ + + + + %%PROJECTNAME%% + + + + + + + + + + + + diff --git a/daegsrv/mobile/eliom_loader.ml b/daegsrv/mobile/eliom_loader.ml new file mode 100644 index 0000000..8614004 --- /dev/null +++ b/daegsrv/mobile/eliom_loader.ml @@ -0,0 +1,200 @@ +(* This file was generated by Ocsigen-start. + Feel free to use it, modify it, and redistribute it as you wish. *) + +(* Load Eliom client-side program after storing global data in + localStorage. Compile as follos: + + ocamlfind ocamlc \ + -package js_of_ocaml,js_of_ocaml.ppx,lwt_ppx \ + -linkpkg -o eliom_loader.byte \ + eliom_loader.ml + + js_of_ocaml eliom_loader.byte +*) + +module XmlHttpRequest = Js_of_ocaml_lwt.XmlHttpRequest + +(* Debug mode. Set to true if you want to use the debug mode. Used by "log". + *) +let debug = false + +(* If debug mode is activated, a paragraph is created and a message is printed + * in the console. + *) +let log = + if debug + then (fun s -> + Js_of_ocaml.Firebug.console##log (Js_of_ocaml.Js.string s); + let p = Js_of_ocaml.Dom_html.createP Js_of_ocaml.Dom_html.document in + p##.style##.color := Js_of_ocaml.Js.string "#64b5f6"; + Js_of_ocaml.Dom.appendChild p + (Js_of_ocaml.Dom_html.document##createTextNode (Js_of_ocaml.Js.string s)); + let container = Js_of_ocaml.Dom_html.getElementById "app-container" in + Js_of_ocaml.Dom.appendChild container p) + else fun s -> () + +(* Reference used by the binding to fetchUpdate to know if update has been done + * or if it failed. + *) +let update_failed = ref false +let data_upload_failed = ref false + +(* Get the Eliom server URL where updates must be fetched. *) +let url = + Js_of_ocaml.Js.Optdef.case + Js_of_ocaml.Js.Unsafe.global##.___eliom_server_ + (fun () -> "127.0.0.1:8080/__global_data__") + (fun server -> Js_of_ocaml.Js.to_string server ^ "/__global_data__") + +(* Get the local storage object. Fail if local storage is not supported. *) +let storage () = + Js_of_ocaml.Js.Optdef.case + Js_of_ocaml.Dom_html.window##.localStorage + (fun () -> failwith "Browser storage not supported") + (fun v -> v) + +(* This function is called when updating the files. It changes the class of the + * main container in index.html and add a button with an error message if + * something went wrong. + *) +let rec add_retry_button wake msg = + let container = Js_of_ocaml.Dom_html.getElementById "app-container" in + let p = Js_of_ocaml.Dom_html.createP Js_of_ocaml.Dom_html.document in + let btn = Js_of_ocaml.Dom_html.createButton Js_of_ocaml.Dom_html.document in + (* Set error class *) + container##.className := Js_of_ocaml.Js.string "app-error"; + (* Error message paragraph *) + Js_of_ocaml.Dom.appendChild p + (Js_of_ocaml.Dom_html.document##createTextNode (Js_of_ocaml.Js.string msg)); + p##.id := Js_of_ocaml.Js.string "retry-message"; + (* Retry button *) + Js_of_ocaml.Dom.appendChild btn + (Js_of_ocaml.Dom_html.document##createTextNode + (Js_of_ocaml.Js.string "Retry")); + btn##.onclick := + Js_of_ocaml.Dom_html.handler (fun _ -> + Js_of_ocaml.Dom.removeChild container p; + container##.className := Js_of_ocaml.Js.string "app blink"; + if !update_failed + then ( + update_failed := false; + ignore Js_of_ocaml.Js.Unsafe.global##.chcp##fetchUpdate); + if !data_upload_failed + then ( + data_upload_failed := false; + Lwt.async (fun () -> get_data wake)); + Js_of_ocaml.Js._false); + btn##.id := Js_of_ocaml.Js.string "retry-button"; + Js_of_ocaml.Dom.appendChild p btn; + Js_of_ocaml.Dom.appendChild container p + +and get_data wake = + let%lwt {XmlHttpRequest.content; code} = XmlHttpRequest.get url in + if code = 200 + then ( + log "Got global data"; + (storage ())##setItem + (Js_of_ocaml.Js.string "__global_data") + (Js_of_ocaml.Js.string content); + Lwt.wakeup wake ()) + else ( + log "Could not get global data"; + if not (!update_failed || !data_upload_failed) + then ( + data_upload_failed := true; + add_retry_button wake + "Cannot connect to the server. Please make sure that this app has access to a data connection.")); + Lwt.return_unit + +(* Get the URL saved in the JavaScript variables "___eliom_html_url_" defined in + * index.html and go this location. + *) +let redirect () = + Js_of_ocaml.Js.Optdef.iter Js_of_ocaml.Js.Unsafe.global##.___eliom_html_url_ + (fun url -> Js_of_ocaml.Dom_html.window##.location##replace url) + +let _ = + (* CHCP does not run in the background, so we check for updates on resume *) + ignore + @@ Js_of_ocaml.Dom.addEventListener Js_of_ocaml.Dom_html.document + (Js_of_ocaml.Dom_html.Event.make "resume") + (Js_of_ocaml.Dom.handler (fun _ -> + log "Resume"; + ignore Js_of_ocaml.Js.Unsafe.global##.chcp##fetchUpdate; + Js_of_ocaml.Js._true)) + Js_of_ocaml.Js._false; + (* Create two threads for success callbacks and error callbacks. *) + let wait_success, wake_success = Lwt.wait () in + let wait_error, wake_error = Lwt.wait () in + (* Callback when success. + * [callback ev] will print the event if debug mode is activated. + * Calls by the event chcp_nothingToUpdate. + *) + let callback ev = + Js_of_ocaml.Dom.handler (fun _ -> + log ev; + update_failed := false; + Lwt.wakeup wake_success (); + Js_of_ocaml.Js._true) + in + (* Callback when errors. + * Calls by the event chcp_nothingToUpdate. + *) + let error_callback name = + Js_of_ocaml.Dom.handler (fun ev -> + log + (name ^ ": " + ^ Js_of_ocaml.Js.to_string ev##.detail##.error##.description); + update_failed := true; + if not !data_upload_failed + then + add_retry_button wake_error + (Js_of_ocaml.Js.to_string ev##.detail##.error##.description + ^ ". Please try again later."); + Js_of_ocaml.Js.bool true) + in + (* Callback to print a message *) + let status_callback name = + Js_of_ocaml.Dom.handler (fun ev -> log name; Js_of_ocaml.Js.bool true) + in + (* Binding to chcp_nothingToUpdate. Calls [callback ev]. *) + List.iter + (fun ev -> + ignore + @@ Js_of_ocaml.Dom.addEventListener Js_of_ocaml.Dom_html.document + (Js_of_ocaml.Dom_html.Event.make ev) + (callback ev) Js_of_ocaml.Js._false) + ["chcp_nothingToUpdate"]; + (* Binding to chcp_updateLoadFailed, chcp_updateInstallFailed and + * chcp_assetsInstallationError. It calls [error_callback ev]. + *) + List.iter + (fun ev -> + ignore + @@ Js_of_ocaml.Dom.addEventListener Js_of_ocaml.Dom_html.document + (Js_of_ocaml.Dom_html.Event.make ev) + (error_callback ev) Js_of_ocaml.Js._false) + [ "chcp_updateLoadFailed" + ; "chcp_updateInstallFailed" + ; "chcp_assetsInstallationError" ]; + (* Binding to other chcp events. It calls [status_callback ev] which will only + * print the event. + *) + List.iter + (fun ev -> + ignore + @@ Js_of_ocaml.Dom.addEventListener Js_of_ocaml.Dom_html.document + (Js_of_ocaml.Dom_html.Event.make ev) + (status_callback ev) Js_of_ocaml.Js._false) + [ "chcp_updateIsReadyToInstall" + ; "chcp_beforeInstall" + ; "chcp_nothingToInstall" + ; "chcp_updateInstalled" + ; "chcp_beforeAssetsInstalledOnExternalStorage" + ; "chcp_assetsInstalledOnExternalStorage" ]; + Lwt.async @@ fun () -> + let%lwt _ = Js_of_ocaml_lwt.Lwt_js_events.onload () in + let%lwt _ = get_data wake_error in + let%lwt _ = wait_error in + let%lwt _ = wait_success in + Lwt.return (redirect ()) diff --git a/daegsrv/mobile/index.html.in b/daegsrv/mobile/index.html.in new file mode 100644 index 0000000..5de34e9 --- /dev/null +++ b/daegsrv/mobile/index.html.in @@ -0,0 +1,36 @@ + + + + + + + + + + %%MOBILE_APP_NAME%% + + + + + + + + + diff --git a/daegsrv/mobile/res/.gitignore b/daegsrv/mobile/res/.gitignore new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/daegsrv/mobile/res/.gitignore @@ -0,0 +1 @@ + diff --git a/daegsrv/mobile/www/css/index.css b/daegsrv/mobile/www/css/index.css new file mode 100644 index 0000000..c340091 --- /dev/null +++ b/daegsrv/mobile/www/css/index.css @@ -0,0 +1,96 @@ +/** + * This file is used in index.html when the mobile application starts. + * - *app* and *blink* classes defines the style for the app loader image. + * - *retry-button* and *app-error* is used when an error occurs while updating + * the application. If an error occurs, *app-error* will replace *app*. + * - the logo (saved in img/logo.png) is set as background of the DOM elements + * which contains *app* or *app-error* class. + * + * See eliom_loader.ml and index.html for more information. + */ + +/* ------------------------------------------------------------- */ +/* Defines rules for the animation. It is used by *blink* class. */ + +@keyframes fade { + from { opacity: 1.0; } + 50% { opacity: 0.4; } + to { opacity: 1.0; } +} + +@-webkit-keyframes fade { + from { opacity: 1.0; } + 50% { opacity: 0.4; } + to { opacity: 1.0; } +} + +* { + -webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */ +} + +body { + -webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */ + -webkit-text-size-adjust: none; /* prevent webkit from resizing text to fit */ + -webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */ + background-color:#FFFFFF; + font-family:'HelveticaNeue-Light', 'HelveticaNeue', Helvetica, Arial, sans-serif; + height:100%; + margin:0px; + padding:0px; + width:100%; +} + +/* --------------------- */ +/* app and error classes */ + +/* Portrait layout (default) */ +.app, /* text area height */ +.app-error{ + background:url(../img/logo.png) no-repeat center top; /* 170px x 200px */ + position:absolute; /* position in the center of the screen */ + left:50%; + top:50%; + height:50px; /* text area height */ + width:225px; /* text area width */ + text-align:center; + padding:180px 0px 0px 0px; /* image height is 200px (bottom 20px are overlapped with text) */ + margin:-115px 0px 0px -112px; /* offset vertical: half of image height and text area height */ + /* offset horizontal: half of text area width */ + font-size: 16px; +} + +/* Landscape layout (with min-width) */ +@media screen and (min-aspect-ratio: 1/1) and (min-width:400px) { + .app { + padding:75px 0px 75px 170px; /* padding-top + padding-bottom + text area = image height */ + margin:-90px 0px 0px -198px; /* offset vertical: half of image height */ + } + .app-error { + padding:40px 0px 75px 420px; /* padding-top + padding-bottom + text area = image height */ + margin:-90px 0px 0px -450px; /* offset vertical: half of image height */ + /* offset horizontal: half of image width and text area width */ + } +} + +/* -------------------------------- */ +/* This class is the fade animation */ + +.blink { + animation:fade 3000ms infinite; + -webkit-animation:fade 3000ms infinite; +} + +/* --------------------------------------------------------------- */ +/* Style for the retry button when an error occurs while updating */ + +#retry-button { + width: 100%; + display: block; + margin-top: 1.8rem; + margin-bottom: 1.8rem; + background-color: #64b5f6; + color: white; + height: 35px; + border: none; + font-size: 16px; +} diff --git a/daegsrv/mobile/www/img/logo.png b/daegsrv/mobile/www/img/logo.png new file mode 100644 index 0000000..3c302a9 Binary files /dev/null and b/daegsrv/mobile/www/img/logo.png differ diff --git a/daegsrv/sass/daegsrv.scss b/daegsrv/sass/daegsrv.scss new file mode 100644 index 0000000..9e8ae87 --- /dev/null +++ b/daegsrv/sass/daegsrv.scss @@ -0,0 +1,14 @@ +@import "font-awesome.min.css"; +@import "ot_buttons.css"; +@import "ot_carousel.css"; +@import "ot_sticky.css"; +@import "ot_datetime.css"; +@import "ot_drawer.css"; +@import "ot_icons.css"; +@import "ot_picture_uploader.css"; +@import "ot_popup.css"; +@import "ot_spinner.css"; +@import "ot_page_transition.css"; +@import "ot_tongue.css"; +@import "os"; +@import "demo"; diff --git a/daegsrv/sass/demo.scss b/daegsrv/sass/demo.scss new file mode 100644 index 0000000..ef5d49d --- /dev/null +++ b/daegsrv/sass/demo.scss @@ -0,0 +1,178 @@ +/* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. +*/ +.demo-carousel1 { + display: flex; + justify-content: center; + .demo-carousel1-box { + position: relative; + margin: auto; + } + .ot-carousel { + width: 300px; + height: 200px; + } + .demo-carousel1-page { + width: 100%; + height: 100%; + padding: 16px; + color: white; + } + .demo-carousel1-page-1 { + background-color: #49b2cc; + } + .demo-carousel1-page-2 { + background-color: #dddd55; + } + .demo-carousel1-page-3 { + background-color: #c14768; + } + .demo-carousel1-page-4 { + background-color: #45bf7d; + } + .ot-bullet-nav { + position: absolute; + bottom: 16px; + right: 16px; + margin: 0; + } + .ot-car-prev, + .ot-car-next { + position: absolute; + top: 75px; + width: 50px; + height: 50px; + color: white; + outline: none; + } + .ot-car-prev { + left: 0; + } + .ot-car-next { + right: 0; + } +} +.os-page-demo-carousel2 { + .demo-carousel2 { + margin: 0 -16px; + } + .ot-carousel { + width: 100%; + height: auto; + } + .demo-carousel2-page { + padding: 16px; + p { + text-align: justify; + } + } + .demo-carousel2-page-1 { + background-color: white; + } + .demo-carousel2-page-2 { + background-color: #ffffee; + } + .demo-carousel2-page-3 { + background-color: #ffddee; + } + .demo-carousel2-page-4 { + background-color: #ddffee; + } + .demo-carousel2-tabs { + position: sticky; + background-color: white; + z-index: 1; + top: 50px; + } +} +.os-page-demo-carousel3 { + .demo-prev, + .demo-next { + width: 20px; + height: 20px; + background-color: #6ae; + color: white; + } +} +.os-page-demo-notif, +.os-page-demo-react { + input:not([type]) { + background-color: #eee; + } +} +.os-page-demo-links .demo-static-img { + width: 300px; +} + +.os-page-demo-transition { + .demo-list { + padding-left: 0; + list-style-type: none; + } + + .demo-list-item { + width: 100%; + height: 150px; + font-size: 20px; + padding-top: 65px; + text-align: center; + } + + .demo-list-item > a:visited, + .demo-list-item > a { + color: white; + } + + .demo-list-item.demo-list-item-0 { + background-color: #b1eb00; + } + .demo-list-item.demo-list-item-1 { + background-color: #53baf3; + } + .demo-list-item.demo-list-item-2 { + background-color: #ff85cb; + } + .demo-list-item.demo-list-item-3 { + background-color: #f4402c; + } + .demo-list-item.demo-list-item-4 { + background-color: #ffac00; + } + + .demo-button { + margin: 0 auto; + padding: 20px; + width: 200px; + font-size: 20px; + text-align: center; + background-color: #ffffee; + } +} +.demo-tongue { + display: flex; + flex-direction: column; + .demo-tongue-1 { + background-color: #ffffee; + height: 100px; + } + .demo-tongue-2 { + background-color: #ffddee; + height: 150px; + } + .demo-tongue-3 { + background-color: #ddffee; + height: 80px; + } + .demo-tongue-4 { + background-color: #ddeeff; + height: 200px; + } + .demo-tongue-5 { + background-color: #eeccff; + height: 130px; + } + .demo-tongue-6 { + background-color: #ffeeaa; + height: 300px; + } +} diff --git a/daegsrv/sass/os.scss b/daegsrv/sass/os.scss new file mode 100644 index 0000000..ce25c46 --- /dev/null +++ b/daegsrv/sass/os.scss @@ -0,0 +1,335 @@ +/* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. +*/ + +/* Default CSS for Ocsigen Start */ + +@import url('https://fonts.googleapis.com/css?family=Quicksand'); + +$main-color: #25a; +$dark-color: #445; +$breakpoint1: 720px; + +html { box-sizing: border-box; } +*, *::before, *::after { box-sizing: inherit; } + +body { + margin: 0; + font-size: 14px; + background-color: white; +} + +body, input, textarea, keygen, select, button { + font-family: 'Quicksand', sans-serif; +} + +a, a:visited { + text-decoration: none; + color: #666; + cursor: pointer; +} + +button, input { + border: none; +} + +button, input[type="submit"], input[type="button"] { + cursor: pointer; +} + +.button { + border-radius: 2px; + box-shadow: 0 2px 2px rgba(0,0,0,.28); + background-color: white; + color: #666; + text-transform: uppercase; + font-size: 14px; + padding: 5px 16px; + margin: 5px; +} + +input { + padding: 5px; + border-radius: 2px; +} + +.connected-user-box { + display: flex; + align-items: center; + .fa-user { + height: 50px; + width: 50px; + font-size: 22px; + &::before { + line-height: 50px; + } + } +} + +.os-page-header { + position: fixed; + top: 0; + z-index: 1; + height: 50px; + width: 100%; + background-color: $main-color; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + box-shadow: 0 4px 8px rgba(0,0,0,.28); + padding: 0 20px 0 50px; + .connected-user-box { + color: white; + } +} + +a.os-page-header-app-name { + text-decoration: none; + color: white; + font-size: 30px; +} + +.ot-dr-toggle-button { + color: white; +} + +.os-drawer-menu { + padding: 0; + margin: 0; + overflow-y: auto; + height: 100%; + touch-action: pan-y; + &, ul { + list-style-type: none; + } + .os-drawer-item { + display: block; + padding: 16px; + } + a:hover { + background-color: $dark-color; + color: white; + } + .connected-user-box { + border-bottom: 1px solid #ddd; + } +} + +.os-drawer-submenu { + padding: 0; + > li > a { + display: block; + padding: 5px 5px 5px 50px; + } +} + +.os-body { + padding: 66px 16px; +} + +.os-page-footer { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background-color: #ddd; + padding: 16px; + font-size: 10px; + text-align: center; +} + +.os-welcome-box, .os-settings, .os-sign-in, .os-sign-up, .os-forgot-pwd { + h2 { + font-size: 14px; + margin: 16px 0 4px 0; + } + form, .form-like { + display: flex; + margin: 0 auto; + flex-direction: column; + background-color: #eee; + padding: 16px; + width: 350px; + } + input[type="checkbox"] { + vertical-align: middle; + } + input[type="submit"], button { + margin: 5px auto; + } + input[type="text"], input[type="password"], + input[type="email"], input[type="tel"] { + width: 150px; + display: block; + margin: 5px auto; + } + label { + font-size: 12px; + } +} +.os-sign-up-btn { + background-color: #fb8282; + color: white; +} +.os-forgot-pwd-link { + font-size: 12px; +} + +#os-msg { + position: fixed; + top: 100px; + left: 0; + right: 0; + z-index: 100; + p { + padding: 50px 0; + text-align: center; + background-color: rgba(200,220,240,0.9); + &.os-err { + background-color: rgba(200,0,0,0.7); + color: white; + } + } +} + +.os-page-main .os-page-header .os-connection-box { + position: absolute; + top: 15px; + right: 15px; +} + +.os-page-main:not(.os-connected) { + .os-page-header { + display: block; + height: 40vh; + .os-page-header-app-name { + display: block; + position: absolute; + top: 30vh; + } + } + .os-body { + margin-top: 40vh; + } +} + +@media (max-width: $breakpoint1) { + .os-page-footer { display: none; } + .os-page-header { + .os-connection-box, .os-page-header-app-name, .connected-user-box { + display: none; + } + } + .os-page-main { + .os-page-header .os-connection-box { + display: flex; + flex-direction: column; + position: absolute; + top: 45vh; + left: 0; + width: 100%; + .button { + width: 150px; + margin: 16px auto; + } + } + &.os-not-connected .os-body { + display: none; + } + } + .os-page-header { display: block; } +} + +.os-settings p { + text-align: center; +} + +.os-emails { + text-align: left; + display: flex; + justify-content: center; + ul { + list-style: none; + li { + padding: 5px 10px; + border-bottom: 1px solid #e5e5e5; + display: flex; + justify-content: space-between; + align-items: center; + .os-settings-label { + font-weight: bold; + padding: 4px 10px; + margin-left: 4px; + border-radius: 3px; + } + .os-settings-email { + flex-grow: 2; + padding-right: 20px; + } + .os-main-email { + color: #fff; + background-color: #6cc664; + } + .os-validated-email { + color: #fff; + background-color: #25a; + } + .os-remove-email-button { + box-shadow: none; + font-size: 18px; + margin: 0px; + color: #bd2c00; + } + } + } +} + +.os-avatar { + margin-right: 15px; + width: 35px; +} + +.ot-drawer { + display: flex; + flex-direction: column; + .connected-user-box { + flex-shrink: 0; + } +} +.ot-dr-left .os-avatar { + margin-left: 16px; +} + +/* Tips */ +div.os-tip { + position: absolute; + margin: 20px; + box-shadow: 0px 0px 2px 2px rgba(0, 0, 0, 0.2); + z-index: 1111; + transition: opacity 0.5s; + div.os-tip-bec { + background-color: white; + width: 20px; + height: 20px; + position: absolute; + transform: rotate(45deg); + box-shadow: 0px 0px 2px 2px rgba(0, 0, 0, 0.2); + z-index: -1; + } + div.os-tip-content { + padding: 30px 20px 30px 20px; + background-color: white; + height: 100%; + width: 100%; + } + .os-tip-close { + position: absolute; + top: 5px; + right: 5px; + } + li, + p { + padding-bottom: 10px; + } +} diff --git a/daegsrv/static/css/font-awesome.min.css b/daegsrv/static/css/font-awesome.min.css new file mode 100644 index 0000000..540440c --- /dev/null +++ b/daegsrv/static/css/font-awesome.min.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/daegsrv/static/defaultcss/demo.css b/daegsrv/static/defaultcss/demo.css new file mode 100644 index 0000000..111616d --- /dev/null +++ b/daegsrv/static/defaultcss/demo.css @@ -0,0 +1,122 @@ +/* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. +*/ +.demo-carousel1 { + display: flex; + justify-content: center; } + .demo-carousel1 .demo-carousel1-box { + position: relative; + margin: auto; } + .demo-carousel1 .ot-carousel { + width: 300px; + height: 200px; } + .demo-carousel1 .demo-carousel1-page { + width: 100%; + height: 100%; + padding: 16px; + color: white; } + .demo-carousel1 .demo-carousel1-page-1 { + background-color: #49b2cc; } + .demo-carousel1 .demo-carousel1-page-2 { + background-color: #dddd55; } + .demo-carousel1 .demo-carousel1-page-3 { + background-color: #c14768; } + .demo-carousel1 .demo-carousel1-page-4 { + background-color: #45bf7d; } + .demo-carousel1 .ot-bullet-nav { + position: absolute; + bottom: 16px; + right: 16px; + margin: 0; } + .demo-carousel1 .ot-car-prev, .demo-carousel1 .ot-car-next { + position: absolute; + top: 75px; + width: 50px; + height: 50px; + color: white; + outline: none; } + .demo-carousel1 .ot-car-prev { + left: 0; } + .demo-carousel1 .ot-car-next { + right: 0; } + +.os-page-demo-carousel2 .demo-carousel2 { + margin: 0 -16px; } +.os-page-demo-carousel2 .ot-carousel { + width: 100%; + height: auto; } +.os-page-demo-carousel2 .demo-carousel2-page { + padding: 16px; } + .os-page-demo-carousel2 .demo-carousel2-page p { + text-align: justify; } +.os-page-demo-carousel2 .demo-carousel2-page-1 { + background-color: white; } +.os-page-demo-carousel2 .demo-carousel2-page-2 { + background-color: #ffffee; } +.os-page-demo-carousel2 .demo-carousel2-page-3 { + background-color: #ffddee; } +.os-page-demo-carousel2 .demo-carousel2-page-4 { + background-color: #ddffee; } +.os-page-demo-carousel2 .demo-carousel2-tabs { + position: sticky; + background-color: white; + z-index: 1; + top: 50px; } + +.os-page-demo-carousel3 .demo-prev, .os-page-demo-carousel3 .demo-next { + width: 20px; + height: 20px; + background-color: #6ae; + color: white; } + +.os-page-demo-notif input:not([type]), +.os-page-demo-react input:not([type]) { + background-color: #eee; } + +.os-page-demo-links .demo-static-img { + width: 300px; } + +.os-page-demo-transition .demo-list{ + padding-left: 0; + list-style-type: none; +} + +.os-page-demo-transition .demo-list-item { + width: 100%; + height: 150px; + font-size: 20px; + padding-top: 65px; + text-align: center; +} + +.os-page-demo-transition .demo-list-item>a:visited, +.os-page-demo-transition .demo-list-item>a { + color: white; +} + +.os-page-demo-transition .demo-list-item.demo-list-item-0 { + background-color: #B1EB00 +} +.os-page-demo-transition .demo-list-item.demo-list-item-1 { + background-color: #53BAF3 +} +.os-page-demo-transition .demo-list-item.demo-list-item-2 { + background-color: #FF85CB +} +.os-page-demo-transition .demo-list-item.demo-list-item-3 { + background-color: #F4402C +} +.os-page-demo-transition .demo-list-item.demo-list-item-4 { + background-color: #FFAC00 +} + +.os-page-demo-transition .demo-button { + margin: 0 auto; + padding: 20px; + width: 200px; + font-size: 20px; + text-align: center; + background-color: #ffffee; +} + +/*# sourceMappingURL=.os_temporary_filename.css.map */ diff --git a/daegsrv/static/defaultcss/os.css b/daegsrv/static/defaultcss/os.css new file mode 100644 index 0000000..71d5eb3 --- /dev/null +++ b/daegsrv/static/defaultcss/os.css @@ -0,0 +1,269 @@ +/* This file was generated by Ocsigen Start. + Feel free to use it, modify it, and redistribute it as you wish. +*/ +/* Default CSS for Ocsigen Start */ +@import url("https://fonts.googleapis.com/css?family=Quicksand"); +html { + box-sizing: border-box; } + +*, *::before, *::after { + box-sizing: inherit; } + +body { + margin: 0; + font-size: 14px; + background-color: white; } + +body, input, textarea, keygen, select, button { + font-family: 'Quicksand', sans-serif; } + +a, a:visited { + text-decoration: none; + color: #666; + cursor: pointer; } + +button, input { + border: none; } + +button, input[type="submit"], input[type="button"] { + cursor: pointer; } + +.button { + border-radius: 2px; + box-shadow: 0 2px 2px rgba(0, 0, 0, 0.28); + background-color: white; + color: #666; + text-transform: uppercase; + font-size: 14px; + padding: 5px 16px; + margin: 5px; } + +input { + padding: 5px; + border-radius: 2px; } + +.connected-user-box { + display: flex; + align-items: center; } + .connected-user-box .fa-user { + height: 50px; + width: 50px; + font-size: 22px; } + .connected-user-box .fa-user::before { + line-height: 50px; } + +.os-page-header { + position: fixed; + top: 0; + z-index: 1; + height: 50px; + width: 100%; + background-color: #25a; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.28); + padding: 0 20px 0 50px; } + .os-page-header .connected-user-box { + color: white; } + +a.os-page-header-app-name { + text-decoration: none; + color: white; + font-size: 30px; } + +.ot-dr-toggle-button { + color: white; } + +.os-drawer-menu { + padding: 0; + margin: 0; + overflow-y: auto; + height: 100%; + touch-action: pan-y; } + .os-drawer-menu, .os-drawer-menu ul { + list-style-type: none; } + .os-drawer-menu .os-drawer-item { + display: block; + padding: 16px; } + .os-drawer-menu a:hover { + background-color: #445; + color: white; } + .os-drawer-menu .connected-user-box { + border-bottom: 1px solid #ddd; } + +.os-drawer-submenu { + padding: 0; } + .os-drawer-submenu > li > a { + display: block; + padding: 5px 5px 5px 50px; } + +.os-body { + padding: 66px 16px; } + +.os-page-footer { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background-color: #ddd; + padding: 16px; + font-size: 10px; + text-align: center; } + +.os-welcome-box h2, .os-settings h2, .os-sign-in h2, .os-sign-up h2, .os-forgot-pwd h2 { + font-size: 14px; + margin: 16px 0 4px 0; } +.os-welcome-box form, .os-settings form, .os-sign-in form, .os-sign-up form, .os-forgot-pwd form { + display: flex; + margin: 0 auto; + flex-direction: column; + background-color: #eee; + padding: 16px; + width: 350px; } +.os-welcome-box input[type="checkbox"], .os-settings input[type="checkbox"], .os-sign-in input[type="checkbox"], .os-sign-up input[type="checkbox"], .os-forgot-pwd input[type="checkbox"] { + vertical-align: middle; } +.os-welcome-box input[type="submit"], .os-settings input[type="submit"], .os-sign-in input[type="submit"], .os-sign-up input[type="submit"], .os-forgot-pwd input[type="submit"] { + margin: 5px auto; } +.os-welcome-box input[type="text"], .os-welcome-box input[type="password"], .os-welcome-box input[type="email"], .os-settings input[type="text"], .os-settings input[type="password"], .os-settings input[type="email"], .os-sign-in input[type="text"], .os-sign-in input[type="password"], .os-sign-in input[type="email"], .os-sign-up input[type="text"], .os-sign-up input[type="password"], .os-sign-up input[type="email"], .os-forgot-pwd input[type="text"], .os-forgot-pwd input[type="password"], .os-forgot-pwd input[type="email"] { + width: 150px; + display: block; + margin: 5px auto; } +.os-welcome-box label, .os-settings label, .os-sign-in label, .os-sign-up label, .os-forgot-pwd label { + font-size: 12px; } + +.os-sign-up-btn { + background-color: #fb8282; + color: white; } + +.os-forgot-pwd-link { + font-size: 12px; } + +#os-msg { + position: fixed; + top: 100px; + left: 0; + right: 0; + z-index: 100; } + #os-msg p { + padding: 50px 0; + text-align: center; + background-color: rgba(200, 220, 240, 0.9); } + #os-msg p.os-err { + background-color: rgba(200, 0, 0, 0.7); + color: white; } + +.os-page-main .os-page-header .os-connection-box { + position: absolute; + top: 15px; + right: 15px; } + +.os-page-main:not(.os-connected) .os-page-header { + display: block; + height: 40vh; } + .os-page-main:not(.os-connected) .os-page-header .os-page-header-app-name { + display: block; + position: absolute; + top: 30vh; } +.os-page-main:not(.os-connected) .os-body { + margin-top: 40vh; } + +@media (max-width: 720px) { + .os-page-footer { + display: none; } + + .os-page-header .os-connection-box, .os-page-header .os-page-header-app-name, .os-page-header .connected-user-box { + display: none; } + + .os-page-main .os-page-header .os-connection-box { + display: flex; + flex-direction: column; + position: absolute; + top: 45vh; + left: 0; + width: 100%; } + .os-page-main .os-page-header .os-connection-box .button { + width: 150px; + margin: 16px auto; } + .os-page-main.os-not-connected .os-body { + display: none; } + + .os-page-header { + display: block; } } +.os-settings p { + text-align: center; } + +.os-emails { + text-align: left; + display: flex; + justify-content: center; } + .os-emails ul { + list-style: none; } + .os-emails ul li { + padding: 5px 10px; + border-bottom: 1px solid #e5e5e5; + display: flex; + justify-content: space-between; + align-items: center; } + .os-emails ul li .os-settings-label { + font-weight: bold; + padding: 4px 10px; + margin-left: 4px; + border-radius: 3px; } + .os-emails ul li .os-settings-email { + flex-grow: 2; + padding-right: 20px; } + .os-emails ul li .os-main-email { + color: #fff; + background-color: #6cc664; } + .os-emails ul li .os-validated-email { + color: #fff; + background-color: #25a; } + .os-emails ul li .os-remove-email-button { + box-shadow: none; + font-size: 18px; + margin: 0px; + color: #bd2c00; } + +.os-avatar { + margin-right: 15px; + width: 35px; } + +.ot-drawer { + display: flex; + flex-direction: column; } + .ot-drawer .connected-user-box { + flex-shrink: 0; } + +.ot-dr-left .os-avatar { + margin-left: 16px; } + +/* Tips */ +div.os-tip { + position: absolute; + margin: 20px; + box-shadow: 0px 0px 2px 2px rgba(0, 0, 0, 0.2); + z-index: 1111; } + div.os-tip div.os-tip-bec { + background-color: white; + width: 20px; + height: 20px; + position: absolute; + transform: rotate(45deg); + box-shadow: 0px 0px 2px 2px rgba(0, 0, 0, 0.2); + z-index: -1; } + div.os-tip div.os-tip-content { + padding: 30px 20px 30px 20px; + background-color: white; + height: 100%; + width: 100%; } + div.os-tip .os-tip-close { + position: absolute; + top: 5px; + right: 5px; } + div.os-tip li, + div.os-tip p { + padding-bottom: 10px; } + +/*# sourceMappingURL=.os_temporary_filename.css.map */ diff --git a/daegsrv/static/fonts/FontAwesome.otf b/daegsrv/static/fonts/FontAwesome.otf new file mode 100644 index 0000000..401ec0f Binary files /dev/null and b/daegsrv/static/fonts/FontAwesome.otf differ diff --git a/daegsrv/static/fonts/fontawesome-webfont.eot b/daegsrv/static/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/daegsrv/static/fonts/fontawesome-webfont.eot differ diff --git a/daegsrv/static/fonts/fontawesome-webfont.svg b/daegsrv/static/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/daegsrv/static/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/daegsrv/static/fonts/fontawesome-webfont.ttf b/daegsrv/static/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/daegsrv/static/fonts/fontawesome-webfont.ttf differ diff --git a/daegsrv/static/fonts/fontawesome-webfont.woff b/daegsrv/static/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/daegsrv/static/fonts/fontawesome-webfont.woff differ diff --git a/daegsrv/static/fonts/fontawesome-webfont.woff2 b/daegsrv/static/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/daegsrv/static/fonts/fontawesome-webfont.woff2 differ diff --git a/daegsrv/static/images/ocsigen.png b/daegsrv/static/images/ocsigen.png new file mode 100644 index 0000000..631857a Binary files /dev/null and b/daegsrv/static/images/ocsigen.png differ diff --git a/daegsrv/tools/sort_deps.ml b/daegsrv/tools/sort_deps.ml new file mode 100644 index 0000000..1547a64 --- /dev/null +++ b/daegsrv/tools/sort_deps.ml @@ -0,0 +1,73 @@ +#load "str.cma" + +let space_re = Str.regexp " +" +let edges = Hashtbl.create 128 +let edge_count = Hashtbl.create 128 + +let chop s = + try + let i = String.rindex s '.' in + String.sub s 0 i + with Not_found -> s + +let add_edge target dep = + if target <> dep + then ( + Hashtbl.replace edges dep + (target :: (try Hashtbl.find edges dep with Not_found -> [])); + Hashtbl.replace edge_count target + (1 + try Hashtbl.find edge_count target with Not_found -> 0); + if not (Hashtbl.mem edge_count dep) then Hashtbl.add edge_count dep 0) + +let sort l = + let res = ref [] in + List.iter + (fun (target, deps) -> + let target = chop target in + if not (Hashtbl.mem edge_count target) + then Hashtbl.add edge_count target 0; + List.iter (fun dep -> add_edge target (chop dep)) deps) + l; + let q = Queue.create () in + Hashtbl.iter + (fun target count -> if count = 0 then Queue.add target q) + edge_count; + while not (Queue.is_empty q) do + let n = Queue.take q in + res := n :: !res; + let l = try Hashtbl.find edges n with Not_found -> [] in + Hashtbl.remove edges n; + List.iter + (fun target -> + let c = Hashtbl.find edge_count target - 1 in + Hashtbl.replace edge_count target c; + if c = 0 then Queue.add target q) + l + done; + if Hashtbl.length edges <> 0 + then ( + Format.eprintf "Dependency loop!@."; + exit 1); + List.rev !res + +let _ = + let ch = open_in Sys.argv.(1) in + let lst = ref [] in + (try + while true do + let l = input_line ch in + let l = Str.split space_re l in + match l with + | target :: ":" :: deps -> lst := (target, deps) :: !lst + | _ -> assert false + done + with End_of_file -> ()); + let lst = sort !lst in + let files = Hashtbl.create 128 in + for i = 2 to Array.length Sys.argv - 1 do + Hashtbl.add files (chop Sys.argv.(i)) Sys.argv.(i) + done; + List.iter + (fun f -> + try Format.printf "%s@." (Hashtbl.find files f) with Not_found -> ()) + lst