Compare commits
34 Commits
e39728cb33
...
main
Author | SHA1 | Date | |
---|---|---|---|
77af2e4c82 | |||
025e7c81e9 | |||
1595e150f4 | |||
47885845a3 | |||
9c91bcb6cb | |||
f27154b705 | |||
85750b4de4 | |||
a48ab93b08 | |||
daa41ac8e4 | |||
0927a00960 | |||
39ce636c0a | |||
f361e47e75 | |||
46e4e5c079 | |||
39ebf6a535 | |||
0d854b026a | |||
a9c4b29935 | |||
8905bed454 | |||
0d652ffc90 | |||
26a4f25631 | |||
9278216b4a | |||
414cabb9f8 | |||
9b6e38a313 | |||
4735047682 | |||
f243452783 | |||
a2f2224b96 | |||
89ebf5c792 | |||
aea9415bbd | |||
df89fd87c7 | |||
2305a8a300 | |||
51ee9f00fb | |||
9ab06f0e9c | |||
5ed1771b18 | |||
ef1813f16e | |||
db74e26124 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -59,6 +59,9 @@ build-iPhoneSimulator/
|
||||
# Local database storage
|
||||
data/stgm.db
|
||||
|
||||
# Local configuration
|
||||
config/config.yaml
|
||||
|
||||
# Node modules for Grunt.js
|
||||
node_modules/
|
||||
|
||||
|
21
.rubocop.yml
Normal file
21
.rubocop.yml
Normal file
@ -0,0 +1,21 @@
|
||||
require: rubocop-sequel
|
||||
|
||||
AllCops:
|
||||
NewCops: enable
|
||||
|
||||
Layout/EmptyLinesAroundClassBody:
|
||||
EnforcedStyle: 'empty_lines_except_namespace'
|
||||
Layout/EmptyLinesAroundModuleBody:
|
||||
EnforcedStyle: 'empty_lines_except_namespace'
|
||||
Metrics/ClassLength:
|
||||
Max: 150
|
||||
Style/ClassVars:
|
||||
Enabled: false
|
||||
Style/GlobalVars:
|
||||
Enabled: false
|
||||
Style/MethodCallWithoutArgsParentheses:
|
||||
Enabled: false
|
||||
Style/MethodCallWithArgsParentheses:
|
||||
Enabled: true
|
||||
Style/RedundantReturn:
|
||||
Enabled: false
|
19
.woodpecker.yml
Normal file
19
.woodpecker.yml
Normal file
@ -0,0 +1,19 @@
|
||||
pipeline:
|
||||
style:
|
||||
image: ruby:3.0
|
||||
commands:
|
||||
- 'gem install rake'
|
||||
- 'bundle config set --local path "vendor/bundle"'
|
||||
- 'bundle install'
|
||||
- 'rake test:rubocop'
|
||||
|
||||
gitea_release:
|
||||
image: plugins/gitea-release
|
||||
settings:
|
||||
api_key:
|
||||
from_secret: gitea_api_key
|
||||
base_url: https://git.metaunix.net
|
||||
title: "${CI_COMMIT_TAG}"
|
||||
when:
|
||||
event: tag
|
||||
|
14
Gemfile
14
Gemfile
@ -7,6 +7,16 @@ gem 'puma', '~> 6.1'
|
||||
gem 'sequel', '~> 5.66'
|
||||
gem 'sqlite3', '~> 1.6'
|
||||
|
||||
# Use rerun gem to auto-reload app
|
||||
gem 'rerun'
|
||||
gem 'kramdown', '~> 2.4'
|
||||
|
||||
gem 'pandoc-ruby', '~> 2.1'
|
||||
|
||||
group :development, :test do
|
||||
# Use guard-rack gem to auto-reload app
|
||||
gem 'guard-rack'
|
||||
gem 'wdm', '>= 0.1.0' if Gem.win_platform?
|
||||
|
||||
# rubocop and extensions for code style
|
||||
gem 'rubocop'
|
||||
gem 'rubocop-sequel'
|
||||
end
|
||||
|
66
Gemfile.lock
66
Gemfile.lock
@ -1,26 +1,76 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
ast (2.4.2)
|
||||
coderay (1.1.3)
|
||||
ffi (1.15.5)
|
||||
ffi (1.15.5-x64-mingw-ucrt)
|
||||
formatador (1.1.0)
|
||||
guard (2.18.0)
|
||||
formatador (>= 0.2.4)
|
||||
listen (>= 2.7, < 4.0)
|
||||
lumberjack (>= 1.0.12, < 2.0)
|
||||
nenv (~> 0.1)
|
||||
notiffany (~> 0.0)
|
||||
pry (>= 0.13.0)
|
||||
shellany (~> 0.0)
|
||||
thor (>= 0.18.1)
|
||||
guard-rack (2.2.1)
|
||||
ffi
|
||||
guard (~> 2.3)
|
||||
spoon
|
||||
json (2.6.3)
|
||||
kramdown (2.4.0)
|
||||
rexml
|
||||
listen (3.8.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
lumberjack (1.2.8)
|
||||
method_source (1.0.0)
|
||||
multi_json (1.15.0)
|
||||
mustermann (3.0.0)
|
||||
ruby2_keywords (~> 0.0.1)
|
||||
nenv (0.3.0)
|
||||
nio4r (2.5.8)
|
||||
notiffany (0.1.3)
|
||||
nenv (~> 0.1)
|
||||
shellany (~> 0.0)
|
||||
pandoc-ruby (2.1.7)
|
||||
parallel (1.22.1)
|
||||
parser (3.2.1.1)
|
||||
ast (~> 2.4.1)
|
||||
pry (0.14.2)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
puma (6.1.1)
|
||||
nio4r (~> 2.0)
|
||||
rack (2.2.6.2)
|
||||
rack-protection (3.0.5)
|
||||
rack
|
||||
rainbow (3.1.1)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rerun (0.14.0)
|
||||
listen (~> 3.0)
|
||||
regexp_parser (2.7.0)
|
||||
rexml (3.2.5)
|
||||
rubocop (1.48.0)
|
||||
json (~> 2.3)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.2.0.0)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.26.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.27.0)
|
||||
parser (>= 3.2.1.0)
|
||||
rubocop-sequel (0.3.4)
|
||||
rubocop (~> 1.0)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
sequel (5.66.0)
|
||||
shellany (0.0.1)
|
||||
sinatra (3.0.5)
|
||||
mustermann (~> 3.0)
|
||||
rack (~> 2.2, >= 2.2.4)
|
||||
@ -32,15 +82,25 @@ GEM
|
||||
rack-protection (= 3.0.5)
|
||||
sinatra (= 3.0.5)
|
||||
tilt (~> 2.0)
|
||||
spoon (0.0.6)
|
||||
ffi
|
||||
sqlite3 (1.6.1-x64-mingw-ucrt)
|
||||
sqlite3 (1.6.1-x86_64-linux)
|
||||
thor (1.2.1)
|
||||
tilt (2.1.0)
|
||||
unicode-display_width (2.4.2)
|
||||
|
||||
PLATFORMS
|
||||
x64-mingw-ucrt
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
guard-rack
|
||||
kramdown (~> 2.4)
|
||||
pandoc-ruby (~> 2.1)
|
||||
puma (~> 6.1)
|
||||
rerun
|
||||
rubocop
|
||||
rubocop-sequel
|
||||
sequel (~> 5.66)
|
||||
sinatra (~> 3.0)
|
||||
sinatra-contrib (~> 3.0)
|
||||
|
6
Guardfile
Normal file
6
Guardfile
Normal file
@ -0,0 +1,6 @@
|
||||
guard 'rack' do
|
||||
watch('Gemfile.lock')
|
||||
watch('config.ru')
|
||||
watch('server.rb')
|
||||
watch(%r{^(app)/.*})
|
||||
end
|
17
Rakefile
17
Rakefile
@ -7,14 +7,25 @@ namespace :db do
|
||||
task :migrate do
|
||||
%x{sequel -m 'db/migrations/' 'sqlite://data/stgm.db'}
|
||||
end
|
||||
|
||||
task :import do
|
||||
channel = ENV['channel']
|
||||
puts %x{ruby scan.rb "#{channel}"}
|
||||
end
|
||||
end
|
||||
|
||||
namespace :server do
|
||||
task :dev do
|
||||
%x{ruby server.rb}
|
||||
task :start do
|
||||
system("bundle exec puma -C config/puma.rb")
|
||||
end
|
||||
|
||||
task :reload do
|
||||
%x{rerun --ignore 'assets/*' --ignore 'public/*' --no-notify 'ruby server.rb'}
|
||||
%x{guard}
|
||||
end
|
||||
end
|
||||
|
||||
namespace :test do
|
||||
task :rubocop do
|
||||
system("rubocop app/ server.rb scan.rb")
|
||||
end
|
||||
end
|
||||
|
32
app/config.rb
Normal file
32
app/config.rb
Normal file
@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'yaml'
|
||||
|
||||
# Configuration loader - loads config defaults and an optional config file
|
||||
class Config
|
||||
|
||||
DEFAULT_CONFIG = 'config/defaults.yaml'
|
||||
|
||||
def initialize(config_path)
|
||||
# Load the default config
|
||||
@data = YAML.load_file(DEFAULT_CONFIG)
|
||||
|
||||
# End if the optional config file doesn't exist
|
||||
return unless File.exist?(config_path)
|
||||
|
||||
# If the optional config exists, load it up!
|
||||
@data.merge!(YAML.load_file(config_path))
|
||||
end
|
||||
|
||||
def get(key)
|
||||
bits = key.split('.')
|
||||
value = @data
|
||||
|
||||
bits.each do |bit|
|
||||
value = value[bit]
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
end
|
28
app/helpers.rb
Normal file
28
app/helpers.rb
Normal file
@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# ERB view helper functions
|
||||
module Helpers
|
||||
|
||||
def nullable(value)
|
||||
# Returns the value if it actually exists
|
||||
return value if value && (value != '')
|
||||
|
||||
# Returns a default 'N/a' string
|
||||
return 'N/a'
|
||||
end
|
||||
|
||||
def date_format(date)
|
||||
dt = date.to_datetime
|
||||
return dt.strftime('%B %d, %Y, %I:%M:%S %p')
|
||||
end
|
||||
|
||||
def date_format_input(date)
|
||||
dt = date.to_datetime
|
||||
return dt.strftime('%Y-%m-%dT%H:%M:%S')
|
||||
end
|
||||
|
||||
def serialize(num)
|
||||
return num.to_s.rjust(4, '0')
|
||||
end
|
||||
|
||||
end
|
25
app/models/channel.rb
Normal file
25
app/models/channel.rb
Normal file
@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'fileutils'
|
||||
|
||||
# Model handles YouTube channels
|
||||
class Channel < Sequel::Model
|
||||
|
||||
one_to_many :videos
|
||||
|
||||
def ensure_directory_structure
|
||||
sub_dirs = ['Archive', 'Channel Documents', 'Main', 'Shorts']
|
||||
sub_dirs.each do |d|
|
||||
sub_path = File.join(
|
||||
@values[:directory_path],
|
||||
d
|
||||
)
|
||||
FileUtils.mkdir_p(sub_path)
|
||||
end
|
||||
end
|
||||
|
||||
def open_projects
|
||||
return videos_dataset.exclude(status: 'published').exclude(archived: true).all()
|
||||
end
|
||||
|
||||
end
|
35
app/models/video.rb
Normal file
35
app/models/video.rb
Normal file
@ -0,0 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'fileutils'
|
||||
require 'kramdown'
|
||||
require 'pandoc-ruby'
|
||||
|
||||
# Model handles Video projects
|
||||
class Video < Sequel::Model
|
||||
|
||||
many_to_one :channel
|
||||
|
||||
def ensure_directory_structure
|
||||
sub_dirs = %w[Audio B-Roll Clips Images Export]
|
||||
sub_dirs.each do |d|
|
||||
sub_path = File.join(
|
||||
@values[:directory_path],
|
||||
d
|
||||
)
|
||||
FileUtils.mkdir_p(sub_path)
|
||||
end
|
||||
end
|
||||
|
||||
def parse_script
|
||||
Kramdown::Document.new(@values[:script]).to_html
|
||||
end
|
||||
|
||||
def import_script
|
||||
scripts = Dir.glob("#{@values[:directory_path]}/*Script.docx")
|
||||
script_content = PandocRuby.convert([scripts[0].dump()], from: :docx, to: :markdown)
|
||||
@values[:script] = script_content
|
||||
# save changes to the model
|
||||
save_changes()
|
||||
end
|
||||
|
||||
end
|
7
app/routes.rb
Normal file
7
app/routes.rb
Normal file
@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'routes/index'
|
||||
|
||||
require_relative 'routes/channel'
|
||||
|
||||
require_relative 'routes/video'
|
22
app/routes/api_v1.rb
Normal file
22
app/routes/api_v1.rb
Normal file
@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class StageManager
|
||||
# Controller to handle API v1 routes
|
||||
class ApiV1Controller
|
||||
|
||||
get '/health' do
|
||||
json status: 'success'
|
||||
end
|
||||
|
||||
get '/channels' do
|
||||
channels = Channel.all().map!(&:to_hash)
|
||||
json channels
|
||||
end
|
||||
|
||||
get '/videos' do
|
||||
videos = Video.all().map!(&:to_hash)
|
||||
json videos
|
||||
end
|
||||
|
||||
end
|
||||
end
|
100
app/routes/channel.rb
Normal file
100
app/routes/channel.rb
Normal file
@ -0,0 +1,100 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class StageManager
|
||||
# Channel that handles channel top-level routes
|
||||
class ChannelController
|
||||
|
||||
get '/' do
|
||||
redirect '/channel/list'
|
||||
end
|
||||
get '/list' do
|
||||
channels = Channel.reverse(:updated_at).all()
|
||||
erb :'channel/list', locals: {
|
||||
title: 'List of channels',
|
||||
channels: channels
|
||||
}
|
||||
end
|
||||
|
||||
get '/create' do
|
||||
erb :'channel/create', locals: {
|
||||
title: 'Create new channel',
|
||||
base_directory: $conf.get('stgm.base_directory')
|
||||
}
|
||||
end
|
||||
post '/create' do
|
||||
channel = Channel.create(
|
||||
name: params[:channel_name],
|
||||
directory_path: params[:channel_dir],
|
||||
description: params[:channel_description]
|
||||
)
|
||||
|
||||
# create supporting directory structure
|
||||
Dir.mkdir(channel.directory_path)
|
||||
channel.ensure_directory_structure()
|
||||
|
||||
redirect "/channel/#{channel.id}"
|
||||
end
|
||||
|
||||
get '/:channel_id' do
|
||||
channel = Channel.where(id: params[:channel_id]).first()
|
||||
channel_videos = channel.videos_dataset.reverse(:updated_at).limit(10).all()
|
||||
erb :'channel/view', locals: {
|
||||
title: channel.name,
|
||||
channel: channel,
|
||||
channel_videos: channel_videos
|
||||
}
|
||||
end
|
||||
|
||||
get '/:channel_id/edit' do
|
||||
channel = Channel.where(id: params[:channel_id]).first()
|
||||
erb :'channel/edit', locals: {
|
||||
title: "Editing: #{channel.name}",
|
||||
channel: channel
|
||||
}
|
||||
end
|
||||
post '/:channel_id/edit' do
|
||||
# get channel model and save old directory path
|
||||
channel = Channel.where(id: params[:channel_id]).first()
|
||||
old_path = channel.directory_path
|
||||
|
||||
# edit channel model
|
||||
channel.update(
|
||||
name: params[:channel_name],
|
||||
directory_path: params[:channel_dir],
|
||||
description: params[:channel_description]
|
||||
)
|
||||
|
||||
# edit associate videos' directory paths
|
||||
channel.videos.each do |v|
|
||||
video_path = v.directory_path.sub(old_path, channel.directory_path)
|
||||
v.update(directory_path: video_path)
|
||||
end
|
||||
|
||||
# rename channel directory
|
||||
File.rename(old_path, channel.directory_path)
|
||||
|
||||
# redirect user
|
||||
redirect "/channel/#{channel.id}"
|
||||
end
|
||||
|
||||
post '/:channel_id/edit/:attr' do
|
||||
# find channel and temporarily save the old channel path
|
||||
channel = Channel.where(id: params[:channel_id]).first()
|
||||
attr_to_edit = params[:attr]
|
||||
|
||||
if attr_to_edit == 'directory_path'
|
||||
File.rename(channel.directory_path, params[:value])
|
||||
channel.videos.each do |v|
|
||||
video_path = v.directory_path.sub(channel.directory_path, params[:value])
|
||||
v.update(directory_path: video_path)
|
||||
end
|
||||
end
|
||||
|
||||
channel[attr_to_edit.to_sym] = params[:value]
|
||||
channel.save_changes()
|
||||
|
||||
return 'success'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
20
app/routes/index.rb
Normal file
20
app/routes/index.rb
Normal file
@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class StageManager
|
||||
# Controller to handle top-level pages
|
||||
class IndexController
|
||||
|
||||
get '/' do
|
||||
channels = Channel.reverse(:updated_at).limit(10).all()
|
||||
videos = Video.reverse(:updated_at).limit(10).all()
|
||||
active_projects = Video.where(starred: true).reverse(:updated_at).limit($conf.get('stgm.max_stars'))
|
||||
erb :index, locals: {
|
||||
title: 'Dashboard',
|
||||
channels: channels,
|
||||
videos: videos,
|
||||
active_projects: active_projects
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
end
|
194
app/routes/video.rb
Normal file
194
app/routes/video.rb
Normal file
@ -0,0 +1,194 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'fileutils'
|
||||
|
||||
class StageManager
|
||||
# Controller to handle top-level video routes
|
||||
class VideoController
|
||||
|
||||
get '/' do
|
||||
redirect '/video/list'
|
||||
end
|
||||
get '/list' do
|
||||
videos = Video.reverse(:updated_at).all()
|
||||
erb :'video/list', locals: {
|
||||
title: 'List of videos',
|
||||
videos: videos
|
||||
}
|
||||
end
|
||||
|
||||
get '/create' do
|
||||
# check if there's a channel specified
|
||||
selected_channel = params[:channel].to_i() if params.key?(:channel)
|
||||
|
||||
channels = Channel.all()
|
||||
erb :'video/create', locals: {
|
||||
title: 'Create new video',
|
||||
channels: channels,
|
||||
selected_channel: selected_channel
|
||||
}
|
||||
end
|
||||
post '/create' do
|
||||
channel = Channel.where(id: params[:video_channel]).first()
|
||||
video_serial = params[:video_serial].to_s.rjust(4, '0')
|
||||
video_path = File.join(
|
||||
channel.directory_path,
|
||||
'Main',
|
||||
"##{video_serial} - #{params[:video_name]}"
|
||||
)
|
||||
|
||||
video = Video.create(
|
||||
serial: params[:video_serial],
|
||||
name: params[:video_name],
|
||||
channel_id: params[:video_channel],
|
||||
directory_path: video_path,
|
||||
description: params[:video_description],
|
||||
script: "# Introduction\n\n# Body\n\n# Conclusions"
|
||||
)
|
||||
|
||||
# create supporting directory structure
|
||||
Dir.mkdir(video_path)
|
||||
video.ensure_directory_structure()
|
||||
|
||||
redirect "/video/#{video.id}"
|
||||
end
|
||||
|
||||
get '/:video_id' do
|
||||
video = Video.where(id: params[:video_id]).first()
|
||||
erb :'video/view', locals: {
|
||||
title: video.name,
|
||||
video: video
|
||||
}
|
||||
end
|
||||
|
||||
get '/:video_id/script' do
|
||||
video = Video.where(id: params[:video_id]).first()
|
||||
erb :'video/script', locals: {
|
||||
title: "Script: #{video.name}",
|
||||
video: video
|
||||
}
|
||||
end
|
||||
|
||||
get '/:video_id/edit' do
|
||||
video = Video.where(id: params[:video_id]).first()
|
||||
channels = Channel.all()
|
||||
erb :'video/edit', locals: {
|
||||
title: "Editing: #{video.name}",
|
||||
video: video,
|
||||
channels: channels
|
||||
}
|
||||
end
|
||||
post '/:video_id/edit' do
|
||||
channel = Channel.where(id: params[:video_channel]).first()
|
||||
video_serial = params[:video_serial].to_s.rjust(4, '0')
|
||||
video_path = File.join(
|
||||
channel.directory_path,
|
||||
'Main',
|
||||
"##{video_serial} - #{params[:video_name]}"
|
||||
)
|
||||
|
||||
# find video and temporarily save the old video path
|
||||
video = Video.where(id: params[:video_id]).first()
|
||||
old_path = video.directory_path
|
||||
|
||||
# edit video attributes
|
||||
video.update(
|
||||
name: params[:video_name],
|
||||
serial: params[:video_serial],
|
||||
channel_id: params[:video_channel],
|
||||
directory_path: video_path,
|
||||
description: params[:video_description]
|
||||
)
|
||||
|
||||
# rename the video project directory
|
||||
File.rename(old_path, video_path)
|
||||
|
||||
# redirect the user
|
||||
redirect "/video/#{video.id}"
|
||||
end
|
||||
|
||||
post '/:video_id/edit/:attr' do
|
||||
# find video and temporarily save the old video path
|
||||
video = Video.where(id: params[:video_id]).first()
|
||||
attr_to_edit = params[:attr]
|
||||
|
||||
# if we update the video's serial, we need to also update the directory path
|
||||
if attr_to_edit == 'serial'
|
||||
old_path = video.directory_path
|
||||
new_path = video.directory_path.sub("##{video.serial}", "##{params[:value]}")
|
||||
File.rename(old_path, new_path)
|
||||
video[:directory_path] = new_path
|
||||
end
|
||||
|
||||
video[attr_to_edit.to_sym] = params[:value]
|
||||
video.save_changes()
|
||||
|
||||
return 'success'
|
||||
end
|
||||
|
||||
get '/:video_id/archive' do
|
||||
# find the video
|
||||
video = Video.where(id: params[:video_id]).first()
|
||||
video_base = File.basename(video.directory_path)
|
||||
|
||||
# move project to channel's archive
|
||||
archive_dir = File.join(
|
||||
video.channel.directory_path,
|
||||
'Archive',
|
||||
video_base
|
||||
)
|
||||
FileUtils.mv(video.directory_path, archive_dir)
|
||||
|
||||
# mark the video as archived and update directory
|
||||
video.update(
|
||||
directory_path: archive_dir,
|
||||
archived: true
|
||||
)
|
||||
|
||||
redirect "/video/#{video.id}"
|
||||
end
|
||||
|
||||
get '/:video_id/unarchive' do
|
||||
# find the video
|
||||
video = Video.where(id: params[:video_id]).first()
|
||||
video_base = File.basename(video.directory_path)
|
||||
|
||||
# move project to channel's archive
|
||||
active_dir = File.join(
|
||||
video.channel.directory_path,
|
||||
'Main',
|
||||
video_base
|
||||
)
|
||||
FileUtils.mv(video.directory_path, active_dir)
|
||||
|
||||
# mark the video as archived and update directory
|
||||
video.update(
|
||||
directory_path: active_dir,
|
||||
archived: false
|
||||
)
|
||||
|
||||
redirect "/video/#{video.id}"
|
||||
end
|
||||
|
||||
get '/:video_id/edit/script' do
|
||||
video = Video.where(id: params[:video_id]).first()
|
||||
erb :'video/edit-script', locals: {
|
||||
title: "Editing script: #{video.name}",
|
||||
video: video
|
||||
}
|
||||
end
|
||||
post '/:video_id/edit/script' do
|
||||
# find video and temporarily save the old video path
|
||||
video = Video.where(id: params[:video_id]).first()
|
||||
|
||||
# edit video attributes
|
||||
video.update(
|
||||
script: params[:video_script]
|
||||
)
|
||||
|
||||
# redirect the user
|
||||
redirect "/video/#{video.id}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
37
assets/coffee/wyrm.coffee
Normal file
37
assets/coffee/wyrm.coffee
Normal file
@ -0,0 +1,37 @@
|
||||
$(document).ready(() ->
|
||||
$('.channel-path').click(updateAttribute)
|
||||
|
||||
$('.video-active').click(updateAttribute)
|
||||
|
||||
$('.video-status').click(updateAttribute)
|
||||
|
||||
$('.video-serial').click(updateAttribute)
|
||||
)
|
||||
|
||||
updateAttribute = (e) ->
|
||||
elem = $(this)
|
||||
type = elem.data('type')
|
||||
attr = elem.data('attribute')
|
||||
newValue = prompt('Enter new ' + type + ' ' + attr + ':', elem.find('span').text())
|
||||
if newValue
|
||||
if attr == 'status'
|
||||
newValue = newValue.toLowerCase()
|
||||
if attr == 'starred'
|
||||
newValue = (String(newValue).toLowerCase() == 'true')
|
||||
payload =
|
||||
value: newValue
|
||||
objectId = $('#' + type + '-id').val()
|
||||
url = '/' + type + '/' + objectId + '/edit/' + attr
|
||||
$.post(url, payload, (data) ->
|
||||
if data == 'success'
|
||||
if attr == 'status'
|
||||
newValue = capitalizeWord(newValue)
|
||||
if attr == 'starred'
|
||||
newValue = if newValue then 'True' else 'False'
|
||||
elem.find('span').text(newValue)
|
||||
)
|
||||
else
|
||||
console.log('User canceled attribute "' + attr + '" update.')
|
||||
|
||||
capitalizeWord = (str) ->
|
||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
@ -1,11 +1,11 @@
|
||||
$navbar-height: 60px;
|
||||
|
||||
$highlight-color: cornflowerblue;
|
||||
$highlight-color-dark: darken($highlight-color, 10%);
|
||||
$highlight-color: #2980b9;
|
||||
$highlight-color-dark: lighten($highlight-color, 10%);
|
||||
|
||||
body{
|
||||
padding-top: calc($navbar-height + 15px);
|
||||
background: white;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
a{
|
||||
@ -21,13 +21,24 @@ hr{
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#main-wrapper{
|
||||
max-width: 1120px;
|
||||
padding: 15px 25px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
#main-nav{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: $navbar-height;
|
||||
background: $highlight-color;
|
||||
border-bottom: 1px solid #999;
|
||||
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
z-index: 10;
|
||||
|
||||
ul{
|
||||
display: inline-block;
|
||||
@ -44,16 +55,20 @@ hr{
|
||||
h3{
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
margin-left: 10px;
|
||||
margin-left: 25px;
|
||||
margin-bottom: 0;
|
||||
color: black;
|
||||
color: white;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
li > a{
|
||||
color: white;
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover{
|
||||
color: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,8 +76,10 @@ hr{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#channel-header{
|
||||
.channel-name{
|
||||
#channel-header,
|
||||
#video-header{
|
||||
.channel-name,
|
||||
.video-name{
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.channel-created,
|
||||
@ -75,14 +92,22 @@ hr{
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.channel-description{
|
||||
.video-path{
|
||||
margin-top: 10px;
|
||||
font-size: 2rem;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.channel-description,
|
||||
.video-description{
|
||||
margin-top: 10px;
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
#sidebar{
|
||||
background: #ddd;
|
||||
background: #eee;
|
||||
border: 1px solid #666;
|
||||
border-radius: 5px;
|
||||
|
||||
div:not(:last-child){
|
||||
border-bottom: 1px solid #666;
|
||||
@ -98,7 +123,8 @@ hr{
|
||||
}
|
||||
}
|
||||
|
||||
.actions-bar{
|
||||
.actions-bar,
|
||||
.script-controls{
|
||||
padding: 0;
|
||||
|
||||
span{
|
||||
@ -129,5 +155,42 @@ hr{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.clickable-attr{
|
||||
transition: background 230ms ease-in-out;
|
||||
|
||||
&:hover{
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#video_script{
|
||||
max-width: 100%;
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
#video-script{
|
||||
.script{
|
||||
h1{
|
||||
font-size: 4rem;
|
||||
}
|
||||
h2{
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
h3{
|
||||
font-size: 3rem;
|
||||
}
|
||||
h4{
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
h5{
|
||||
font-size: 2.25rem;
|
||||
}
|
||||
h6{
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
9
config.ru
Normal file
9
config.ru
Normal file
@ -0,0 +1,9 @@
|
||||
# Load application config
|
||||
require_relative 'app/config.rb'
|
||||
$conf = Config.new(File.join(__dir__, 'config/config.yaml'))
|
||||
|
||||
# Load Sinatra server
|
||||
require_relative './server.rb'
|
||||
|
||||
# Run application
|
||||
run StageManager
|
11
config/defaults.yaml
Normal file
11
config/defaults.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
stgm:
|
||||
base_directory: '/srv/videos'
|
||||
max_stars: 5
|
||||
|
||||
server:
|
||||
address: '127.0.0.1'
|
||||
port: 4567
|
||||
|
||||
database:
|
||||
adapter: 'sqlite'
|
||||
database: 'data/stgm.db'
|
6
config/puma.rb
Normal file
6
config/puma.rb
Normal file
@ -0,0 +1,6 @@
|
||||
# Load application config
|
||||
require './app/config.rb'
|
||||
$conf = Config.new(File.join(__dir__, 'config/config.yaml'))
|
||||
|
||||
bind_address = "tcp://#{$conf.get('server.address')}:#{$conf.get('server.port')}"
|
||||
bind bind_address
|
@ -1,3 +0,0 @@
|
||||
database:
|
||||
adapter: 'sqlite'
|
||||
database: 'data/stgm.db'
|
@ -5,7 +5,7 @@ Sequel.migration do
|
||||
primary_key :id
|
||||
String :name, null: false
|
||||
String :description
|
||||
Integer :serial, null: false, unique: true
|
||||
String :serial, null: false, default: 'wxyz'
|
||||
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
|
||||
DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP
|
||||
end
|
||||
|
13
db/migrations/0003_add_directory_paths.rb
Normal file
13
db/migrations/0003_add_directory_paths.rb
Normal file
@ -0,0 +1,13 @@
|
||||
Sequel.migration do
|
||||
|
||||
up do
|
||||
add_column(:channels, :directory_path, String)
|
||||
add_column(:videos, :directory_path, String)
|
||||
end
|
||||
|
||||
down do
|
||||
drop_column(:channels, :directory_path)
|
||||
drop_column(:videos, :directory_path)
|
||||
end
|
||||
|
||||
end
|
11
db/migrations/0004_add_script_attribute.rb
Normal file
11
db/migrations/0004_add_script_attribute.rb
Normal file
@ -0,0 +1,11 @@
|
||||
Sequel.migration do
|
||||
|
||||
up do
|
||||
add_column(:videos, :script, String)
|
||||
end
|
||||
|
||||
down do
|
||||
drop_column(:videos, :script)
|
||||
end
|
||||
|
||||
end
|
11
db/migrations/0005_add_video_status.rb
Normal file
11
db/migrations/0005_add_video_status.rb
Normal file
@ -0,0 +1,11 @@
|
||||
Sequel.migration do
|
||||
|
||||
up do
|
||||
add_column(:videos, :status, String, default: 'backlog')
|
||||
end
|
||||
|
||||
down do
|
||||
drop_column(:videos, :status)
|
||||
end
|
||||
|
||||
end
|
11
db/migrations/0006_add_video_archive.rb
Normal file
11
db/migrations/0006_add_video_archive.rb
Normal file
@ -0,0 +1,11 @@
|
||||
Sequel.migration do
|
||||
|
||||
up do
|
||||
add_column(:videos, :archived, TrueClass, default: false)
|
||||
end
|
||||
|
||||
down do
|
||||
drop_column(:videos, :archived)
|
||||
end
|
||||
|
||||
end
|
11
db/migrations/0007_add_video_star.rb
Normal file
11
db/migrations/0007_add_video_star.rb
Normal file
@ -0,0 +1,11 @@
|
||||
Sequel.migration do
|
||||
|
||||
up do
|
||||
add_column(:videos, :starred, TrueClass, default: false)
|
||||
end
|
||||
|
||||
down do
|
||||
drop_column(:videos, :starred)
|
||||
end
|
||||
|
||||
end
|
@ -1,20 +0,0 @@
|
||||
require 'yaml'
|
||||
|
||||
class Config
|
||||
|
||||
def initialize(config_path)
|
||||
@data = YAML::load_file(config_path)
|
||||
end
|
||||
|
||||
def get(key, depth = 0)
|
||||
bits = key.split('.')
|
||||
value = @data
|
||||
|
||||
bits.each do |bit|
|
||||
value = value[bit]
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
end
|
@ -1,25 +0,0 @@
|
||||
helpers do
|
||||
|
||||
def nullable(value)
|
||||
if (value) and (value != '')
|
||||
return value
|
||||
else
|
||||
return 'N/a'
|
||||
end
|
||||
end
|
||||
|
||||
def date_format(date)
|
||||
dt = date.to_datetime
|
||||
return dt.strftime('%B %d, %Y @ %I:%M:%S %p %Z')
|
||||
end
|
||||
|
||||
def date_format_input(date)
|
||||
dt = date.to_datetime
|
||||
return dt.strftime('%Y-%m-%dT%H:%M:%S')
|
||||
end
|
||||
|
||||
def serialize(num)
|
||||
return num.to_s.rjust(4, '0')
|
||||
end
|
||||
|
||||
end
|
@ -1,9 +0,0 @@
|
||||
class Channel < Sequel::Model
|
||||
|
||||
one_to_many :videos
|
||||
|
||||
def openProjects()
|
||||
return 0
|
||||
end
|
||||
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
class Video < Sequel::Model
|
||||
|
||||
many_to_one :channel
|
||||
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
require_relative 'routes/index.rb'
|
||||
|
||||
require_relative 'routes/channel.rb'
|
||||
|
||||
require_relative 'routes/video.rb'
|
@ -1,37 +0,0 @@
|
||||
namespace '/channel' do
|
||||
|
||||
get '' do
|
||||
redirect '/channel/list'
|
||||
end
|
||||
get '/list' do
|
||||
channels = Channel.reverse(:updated_at).all()
|
||||
erb :'channel/list', :locals => {
|
||||
:title => 'List of channels',
|
||||
:channels => channels
|
||||
}
|
||||
end
|
||||
|
||||
get '/create' do
|
||||
erb :'channel/create', :locals => {
|
||||
:title => 'Create new channel'
|
||||
}
|
||||
end
|
||||
post '/create' do
|
||||
channel = Channel.create(
|
||||
name: params[:channel_name],
|
||||
description: params[:channel_description]
|
||||
)
|
||||
|
||||
redirect "/channel/#{channel.id}"
|
||||
end
|
||||
|
||||
get '/:channel_id' do
|
||||
channel = Channel.where(id: params[:channel_id]).first()
|
||||
puts "#{channel.name}"
|
||||
erb :'channel/view', :locals => {
|
||||
:title => channel.name,
|
||||
:channel => channel
|
||||
}
|
||||
end
|
||||
|
||||
end
|
@ -1,12 +0,0 @@
|
||||
namespace '/' do
|
||||
|
||||
get '' do
|
||||
channels = Channel.reverse(:updated_at).limit(10).all()
|
||||
erb :index, :locals => {
|
||||
:title => 'Dashboard',
|
||||
:channels => channels
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,41 +0,0 @@
|
||||
namespace '/video' do
|
||||
|
||||
get '' do
|
||||
redirect '/video/list'
|
||||
end
|
||||
get '/list' do
|
||||
videos = Video.reverse(:updated_at).all()
|
||||
erb :'video/list', :locals => {
|
||||
:title => 'List of videos',
|
||||
:videos => videos
|
||||
}
|
||||
end
|
||||
|
||||
get '/create' do
|
||||
channels = Channel.all()
|
||||
erb :'video/create', :locals => {
|
||||
:title => 'Create new video',
|
||||
:channels => channels
|
||||
}
|
||||
end
|
||||
post '/create' do
|
||||
video = Video.create(
|
||||
serial: params[:video_serial],
|
||||
name: params[:video_name],
|
||||
channel_id: params[:video_channel],
|
||||
description: params[:video_description]
|
||||
)
|
||||
|
||||
redirect "/video/#{video.id}"
|
||||
end
|
||||
|
||||
get '/:video_id' do
|
||||
video = Video.where(id: params[:video_id]).first()
|
||||
puts "#{video.name}"
|
||||
erb :'video/view', :locals => {
|
||||
:title => video.name,
|
||||
:video => video
|
||||
}
|
||||
end
|
||||
|
||||
end
|
57
scan.rb
Executable file
57
scan.rb
Executable file
@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'pathname'
|
||||
require 'sequel'
|
||||
|
||||
require_relative 'app/config'
|
||||
|
||||
# Load configuration file
|
||||
conf = Config.new(File.join(__dir__, 'data/defaults.yaml'))
|
||||
|
||||
# Load the Sequel timestamps plugin
|
||||
Sequel::Model.plugin(:timestamps)
|
||||
# Initialize Sequel gem for database actions
|
||||
DB = Sequel.connect(adapter: conf.get('database.adapter'), database: conf.get('database.database'))
|
||||
# Load models
|
||||
Dir.glob('app/models/*.rb').sort().each { |f| require f }
|
||||
|
||||
# fail if the script is being called incorrectly
|
||||
abort('You must supply a channel name!') unless ARGV.length == 1
|
||||
|
||||
channel = Channel.where(name: ARGV[0]).first()
|
||||
channel_dir = File.join(channel.directory_path, 'Main')
|
||||
subs = Dir["#{channel_dir}/*"]
|
||||
|
||||
subs.each do |d|
|
||||
# get folder basename
|
||||
dir_name = File.basename(d)
|
||||
|
||||
# parse video serial from folder name
|
||||
serial_raw = dir_name[0..5].strip()
|
||||
video_serial = serial_raw.slice(0)
|
||||
|
||||
# parse video name from folder name
|
||||
video_name = dir_name.split(' - ')[1]
|
||||
|
||||
# check if a video by the same serial number exists for the channel
|
||||
db_results = Video.where(serial: video_serial, channel_id: channel.id).all()
|
||||
|
||||
# skip to next video if the video already exists
|
||||
next unless db_results.empty?()
|
||||
|
||||
# add video project to DB if there's no existing project
|
||||
video = Video.create(
|
||||
serial: video_serial,
|
||||
name: video_name,
|
||||
channel_id: channel.id,
|
||||
directory_path: d,
|
||||
description: 'TODO - imported from storage.',
|
||||
script: "# Introduction\n\n# Body\n\n# Conclusions"
|
||||
)
|
||||
|
||||
video.import_script()
|
||||
|
||||
puts "Successfully added video ##{video.serial} - #{video.name} for channel '#{channel.name}' to the database."
|
||||
end
|
97
server.rb
97
server.rb
@ -1,32 +1,83 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'logger'
|
||||
require 'sequel'
|
||||
require 'sqlite3'
|
||||
require 'sinatra'
|
||||
require 'sinatra/namespace'
|
||||
|
||||
require_relative 'lib/config.rb'
|
||||
|
||||
set :public_folder, __dir__ + '/public'
|
||||
|
||||
set :views, settings.root + '/views'
|
||||
|
||||
# Load configuration file
|
||||
conf = Config.new(File.join(__dir__, 'data/defaults.yaml'))
|
||||
|
||||
# Initialize logging
|
||||
logger = Logger.new(STDOUT)
|
||||
logger.level = Logger::INFO
|
||||
require 'sinatra/base'
|
||||
require 'sinatra/json'
|
||||
require 'rack/protection'
|
||||
|
||||
# Load the Sequel timestamps plugin
|
||||
Sequel::Model.plugin :timestamps
|
||||
Sequel::Model.plugin(:timestamps)
|
||||
# Initialize Sequel gem for database actions
|
||||
DB = Sequel.connect(adapter: conf.get('database.adapter'), database: conf.get('database.database'))
|
||||
DB = Sequel.connect(adapter: $conf.get('database.adapter'), database: $conf.get('database.database'))
|
||||
# Load models
|
||||
require_relative 'lib/models/channel.rb'
|
||||
require_relative 'lib/models/video.rb'
|
||||
Dir.glob('./app/models/*.rb').sort().each { |f| require f }
|
||||
|
||||
# Load helper functions
|
||||
require_relative 'lib/helpers.rb'
|
||||
# Base Sinatra app
|
||||
class StageManager < Sinatra::Base
|
||||
|
||||
# Register route handlers
|
||||
require_relative 'lib/routes.rb'
|
||||
@@my_app = {}
|
||||
|
||||
def self.new(*)
|
||||
self < StageManager ? super : Rack::URLMap.new(@@my_app)
|
||||
end
|
||||
|
||||
def
|
||||
self.map(url) @@my_app[url] = self
|
||||
end
|
||||
|
||||
# Enable and configure sessions
|
||||
enable :sessions
|
||||
|
||||
# Enable rack protection middleware
|
||||
use Rack::Protection
|
||||
|
||||
# Set up static file serving
|
||||
enable :static
|
||||
set :public_folder, File.join(__dir__, '/public')
|
||||
|
||||
# Set up our view engine
|
||||
set :views, File.join(settings.root, '/views')
|
||||
|
||||
# Initialize logging
|
||||
logger = Logger.new($stdout)
|
||||
logger.level = Logger::INFO
|
||||
|
||||
# Load helper functions
|
||||
require_relative 'app/helpers'
|
||||
helpers Helpers
|
||||
|
||||
## Map controllers
|
||||
# Top-level routes controller
|
||||
class IndexController < StageManager
|
||||
|
||||
map '/'
|
||||
|
||||
end
|
||||
|
||||
# Channel routes controller
|
||||
class ChannelController < StageManager
|
||||
|
||||
map '/channel'
|
||||
|
||||
end
|
||||
|
||||
# Video routes controller
|
||||
class VideoController < StageManager
|
||||
|
||||
map '/video'
|
||||
|
||||
end
|
||||
|
||||
# API v1 controller
|
||||
class ApiV1Controller < StageManager
|
||||
|
||||
map '/api/v1'
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Load controllers
|
||||
Dir.glob('./app/routes/*.rb').sort().each { |f| require f }
|
||||
|
@ -14,6 +14,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="columns twelve">
|
||||
<label for="channel_dir">Channel directory:</label>
|
||||
<input class="u-full-width" type="text" id="channel_dir" name="channel_dir" required value="<%= base_directory %>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<label for="channel_description">Channel description:</label>
|
||||
|
34
views/channel/edit.erb
Normal file
34
views/channel/edit.erb
Normal file
@ -0,0 +1,34 @@
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<h1>Editing: <%= channel.name %></h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<form action="/channel/<%= channel.id %>/edit" method="POST" class="u-full-width">
|
||||
<div class="row">
|
||||
<div class="columns twelve">
|
||||
<label for="channel_name">Channel name:</label>
|
||||
<input class="u-full-width" type="text" placeholder="My new channel" id="channel_name" name="channel_name" required value="<%= channel.name %>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="columns twelve">
|
||||
<label for="channel_dir">Channel directory:</label>
|
||||
<input class="u-full-width" type="text" id="channel_dir" name="channel_dir" required value="<%= channel.directory_path %>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<label for="channel_description">Channel description:</label>
|
||||
<textarea class="u-full-width" type="text" placeholder="Description of the channel" id="channel_description" name="channel_description"><%= channel.description %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input class="button-primary u-full-width" type="submit" value="Submit">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
@ -1,6 +1,6 @@
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<h1>Hardware Inventory List</h1>
|
||||
<h1>Channel List</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -6,6 +6,8 @@
|
||||
<h4 class="channel-updated">Last updated at: <%= date_format(channel.updated_at) %></h4>
|
||||
<% end %>
|
||||
|
||||
<p><a href="/video/create?channel=<%= channel.id %>">Create video project</a></p>
|
||||
|
||||
<p class="channel-description"><%= channel.description %></p>
|
||||
</div>
|
||||
|
||||
@ -14,8 +16,11 @@
|
||||
<span><a href="/channel/<%= channel.id %>/edit">Edit <i class="fa-solid fa-pen-to-square"></i></a></span><span>
|
||||
<a href="/channel/<%= channel.id %>/delete">Delete <i class="fa-solid fa-trash"></i></a></span>
|
||||
</div>
|
||||
<div class="channel-path clickable-attr" data-attribute="directory_path" data-type="channel">
|
||||
<p><span><%= channel.directory_path %></span></p>
|
||||
</div>
|
||||
<div class="channel-open">
|
||||
<p>Open projects: <span><%= channel.openProjects() %></span></p>
|
||||
<p>Open projects: <span><%= channel.open_projects().length %></span></p>
|
||||
</div>
|
||||
<div class="channel-videos">
|
||||
<p>Total videos: <span><%= channel.videos.length %></span></p>
|
||||
@ -27,7 +32,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<h3>Channel videos</h3>
|
||||
<h3>Recently updated channel videos:</h3>
|
||||
<% if channel.videos.length > 0 %>
|
||||
<table class="u-full-width">
|
||||
<thead>
|
||||
@ -35,14 +40,16 @@
|
||||
<th>Video name</th>
|
||||
<th>Video serial</th>
|
||||
<th>Video description</th>
|
||||
<th>Last updated</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% channel.videos.each do |v| %>
|
||||
<% channel_videos.each do |v| %>
|
||||
<tr>
|
||||
<td><a href="/video/<%= v.id %>"><%= v.name %></a></td>
|
||||
<td><%= serialize(v.serial) %></td>
|
||||
<td><%= v.description %></td>
|
||||
<td><%= date_format(v.updated_at) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
@ -52,3 +59,5 @@
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="channel-id" value="<%= channel.id %>">
|
||||
|
@ -8,6 +8,53 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<h3>Active projects:</h3>
|
||||
<table class="u-full-width">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Video name</th>
|
||||
<th>Video serial</th>
|
||||
<th>Video channel</th>
|
||||
<th>Last updated</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% active_projects.each do |v| %>
|
||||
<tr>
|
||||
<td><a href="/video/<%= v.id %>"><%= v.name %></a></td>
|
||||
<td><%= serialize(v.serial) %></td>
|
||||
<td><%= v.channel.name %></td>
|
||||
<td><%= date_format(v.updated_at) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Recently updated videos:</h3>
|
||||
<table class="u-full-width">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Video name</th>
|
||||
<th>Video serial</th>
|
||||
<th>Video channel</th>
|
||||
<th>Last updated</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% videos.each do |v| %>
|
||||
<tr>
|
||||
<td><a href="/video/<%= v.id %>"><%= v.name %></a></td>
|
||||
<td><%= serialize(v.serial) %></td>
|
||||
<td><%= v.channel.name %></td>
|
||||
<td><%= date_format(v.updated_at) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Recently updated channels:</h3>
|
||||
<table class="u-full-width">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -21,7 +68,7 @@
|
||||
<% channels.each do |c| %>
|
||||
<tr>
|
||||
<td><a href="/channel/<%= c.id %>"><%= c.name %></a></td>
|
||||
<td><%= nullable(c.openProjects) %></td>
|
||||
<td><%= nullable(c.open_projects().length) %></td>
|
||||
<td><%= nullable(c.videos.length) %></td>
|
||||
<td><%= date_format(c.updated_at) %></td>
|
||||
</tr>
|
||||
|
@ -24,7 +24,7 @@
|
||||
<label for="video_channel">Associated channel:</label>
|
||||
<select class="u-full-width" id="video_channel" name="video_channel" required>
|
||||
<% channels.each do |c| %>
|
||||
<option value="<%= c.id %>"><%= c.name %></option>
|
||||
<option value="<%= c.id %>" <%= 'selected' if c.id == selected_channel %>><%= c.name %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
|
20
views/video/edit-script.erb
Normal file
20
views/video/edit-script.erb
Normal file
@ -0,0 +1,20 @@
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<h1>Editing script for: <%= video.name %></h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<form action="/video/<%= video.id %>/edit/script" method="POST" class="u-full-width">
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<label for="video_script">Video script:</label>
|
||||
<textarea class="u-full-width" type="text" placeholder="Description of the video" id="video_script" name="video_script" rows="30"><%= video.script %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input class="button-primary u-full-width" type="submit" value="Submit">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
43
views/video/edit.erb
Normal file
43
views/video/edit.erb
Normal file
@ -0,0 +1,43 @@
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<h1>Editing: <%= video.name %></h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<form action="/video/<%= video.id %>/edit" method="POST" class="u-full-width">
|
||||
<div class="row">
|
||||
<div class="columns twelve">
|
||||
<label for="video_name">Video name:</label>
|
||||
<input class="u-full-width" type="text" placeholder="My new video" id="video_name" name="video_name" required value="<%= video.name %>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="columns three">
|
||||
<label for="video_serial">Video serial:</label>
|
||||
<input class="u-full-width" type="number" placeholder="0001" id="video_serial" name="video_serial" required value="<%= video.serial %>">
|
||||
</div>
|
||||
|
||||
<div class="columns nine">
|
||||
<label for="video_channel">Associated channel:</label>
|
||||
<select class="u-full-width" id="video_channel" name="video_channel" required>
|
||||
<% channels.each do |c| %>
|
||||
<option value="<%= c.id %>"<% if c.id == video.channel_id %> selected<% end %>><%= c.name %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<label for="video_description">Video description:</label>
|
||||
<textarea class="u-full-width" type="text" placeholder="Description of the video" id="video_description" name="video_description"><%= video.description %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input class="button-primary u-full-width" type="submit" value="Submit">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
@ -1,6 +1,6 @@
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<h1>Hardware Inventory List</h1>
|
||||
<h1>Video List</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
9
views/video/script.erb
Normal file
9
views/video/script.erb
Normal file
@ -0,0 +1,9 @@
|
||||
<div id="video-script" class="row">
|
||||
<div class="twelve columns">
|
||||
<h1>Script: <%= video.name %></h1>
|
||||
|
||||
<hr>
|
||||
|
||||
<%= video.parse_script() %>
|
||||
</div>
|
||||
</div>
|
@ -1,19 +1,49 @@
|
||||
<div id="video-header" class="row">
|
||||
<div class="twelve columns">
|
||||
<h1 class="video-name"><%= video.name %></h1>
|
||||
<h3 class="video-serial"><%= video.channel.name %> - <%= serialize(video.serial) %></h3>
|
||||
<div class="eight columns">
|
||||
<h1 class="video-name"><%= video.name %></h1><%= '<h4>(archived)</h4>' if video.archived %>
|
||||
<h4 class="video-created">Item added at: <%= date_format(video.created_at) %></h4>
|
||||
<% if video.updated_at %>
|
||||
<h4 class="video-updated">Last updated at: <%= date_format(video.updated_at) %></h4>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="video-path"><%= video.directory_path %></h4>
|
||||
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<p class="inventory-actions">
|
||||
<a href="/video/<%= video.id %>/edit"><i class="fa-solid fa-pen-to-square"></i></a>
|
||||
<a href="/video/<%= video.id %>/delete"><i class="fa-solid fa-trash"></i></a>
|
||||
</p>
|
||||
<p class="video-description"><%= video.description %></p>
|
||||
</div>
|
||||
|
||||
<div id="sidebar" class="four columns">
|
||||
<div class="actions-bar">
|
||||
<span><a href="/video/<%= video.id %>/edit">Edit <i class="fa-solid fa-pen-to-square"></i></a></span><% unless video.archived %><span><a href="/video/<%= video.id %>/archive">Archive <i class="fa-solid fa-box-archive"></i></a></span><% else %><span><a href="/video/<%= video.id %>/unarchive">Unarchive <i class="fa-solid fa-box-archive"></i></a></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="video-active clickable-attr" data-attribute="starred" data-type="video">
|
||||
<p>Active: <span><%= video.starred ? 'True' : 'False' %></span></p>
|
||||
</div>
|
||||
<div class="video-channel">
|
||||
<p>Channel: <a href="/channel/<%= video.channel.id %>"><span><%= video.channel.name %></span></a></p>
|
||||
</div>
|
||||
<div class="video-serial clickable-attr" data-attribute="serial" data-type="video">
|
||||
<p>Serial: <span><%= serialize(video.serial) %></span></p>
|
||||
</div>
|
||||
<div class="video-status clickable-attr" data-attribute="status" data-type="video">
|
||||
<p>Status: <span><%= video.status.capitalize() %></span></p>
|
||||
</div>
|
||||
<div class="script-controls">
|
||||
<span><a href="/video/<%= video.id %>/script">View script <i class="fa-solid fa-scroll"></i></a></span><span>
|
||||
<a href="/video/<%= video.id %>/edit/script">Edit script <i class="fa-solid fa-pen-to-square"></i></a></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="video-script" class="row">
|
||||
<div class="twelve columns">
|
||||
<h2>Video script</h2>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="script">
|
||||
<%= video.parse_script() %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="video-id" value="<%= video.id %>">
|
||||
|
Reference in New Issue
Block a user