mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Merge pull request #132 from friendlycrab/cleanup
Remove unused and generated code
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -18,6 +18,13 @@
|
||||
# Packr2 artifacts
|
||||
**/*-packr.go
|
||||
|
||||
# GraphQL generated output
|
||||
pkg/models/generated_*.go
|
||||
ui/v2/src/core/generated-*.tsx
|
||||
|
||||
# packr generated files
|
||||
*-packr.go
|
||||
|
||||
####
|
||||
# Jetbrains
|
||||
####
|
||||
|
||||
@@ -9,10 +9,9 @@ env:
|
||||
- GO111MODULE=on
|
||||
before_install:
|
||||
- echo -e "machine github.com\n login $CI_USER_TOKEN" > ~/.netrc
|
||||
- cd ui/v2
|
||||
- yarn install
|
||||
- CI=false yarn build # TODO: Fix warnings
|
||||
- cd ../..
|
||||
- yarn --cwd ui/v2 install
|
||||
- make generate
|
||||
- CI=false yarn --cwd ui/v2 build # TODO: Fix warnings
|
||||
#- go get -v github.com/mgechev/revive
|
||||
script:
|
||||
#- make lint
|
||||
|
||||
6
Makefile
6
Makefile
@@ -13,9 +13,9 @@ clean:
|
||||
packr2 clean
|
||||
|
||||
# Regenerates GraphQL files
|
||||
.PHONY: gqlgen
|
||||
gqlgen:
|
||||
go run scripts/gqlgen.go
|
||||
.PHONY: generate
|
||||
generate:
|
||||
go generate
|
||||
cd ui/v2 && yarn run gqlgen
|
||||
|
||||
# Runs gofmt -w on the project's source code, modifying any files that do not match its style.
|
||||
|
||||
@@ -88,15 +88,17 @@ TODO
|
||||
|
||||
## Commands
|
||||
|
||||
* `make generate` - Generate Go GraphQL and packr2 files
|
||||
* `make build` - Builds the binary (make sure to build the UI as well... see below)
|
||||
* `make gqlgen` - Regenerate Go GraphQL files
|
||||
* `make ui` - Builds the frontend
|
||||
* `make vet` - Run `go vet`
|
||||
* `make lint` - Run the linter
|
||||
|
||||
## Building a release
|
||||
|
||||
1. cd into the `ui/v2` directory and run `yarn build` to compile the frontend
|
||||
2. cd back to the root directory and run `make build` to build the executable for your current platform
|
||||
1. Run `make generate` to create generated files
|
||||
2. Run `make ui` to compile the frontend
|
||||
3. Run `make build` to build the executable for your current platform
|
||||
|
||||
## Cross compiling
|
||||
|
||||
|
||||
2
main.go
2
main.go
@@ -1,3 +1,5 @@
|
||||
//go:generate go run github.com/99designs/gqlgen
|
||||
//go:generate go run github.com/gobuffalo/packr/v2/packr2
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,453 +0,0 @@
|
||||
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ConfigGeneralInput struct {
|
||||
// Array of file paths to content
|
||||
Stashes []string `json:"stashes"`
|
||||
// Path to the SQLite database
|
||||
DatabasePath *string `json:"databasePath"`
|
||||
// Path to generated files
|
||||
GeneratedPath *string `json:"generatedPath"`
|
||||
}
|
||||
|
||||
type ConfigGeneralResult struct {
|
||||
// Array of file paths to content
|
||||
Stashes []string `json:"stashes"`
|
||||
// Path to the SQLite database
|
||||
DatabasePath string `json:"databasePath"`
|
||||
// Path to generated files
|
||||
GeneratedPath string `json:"generatedPath"`
|
||||
}
|
||||
|
||||
type ConfigInterfaceInput struct {
|
||||
// Custom CSS
|
||||
CSS *string `json:"css"`
|
||||
CSSEnabled *bool `json:"cssEnabled"`
|
||||
}
|
||||
|
||||
type ConfigInterfaceResult struct {
|
||||
// Custom CSS
|
||||
CSS *string `json:"css"`
|
||||
CSSEnabled *bool `json:"cssEnabled"`
|
||||
}
|
||||
|
||||
// All configuration settings
|
||||
type ConfigResult struct {
|
||||
General *ConfigGeneralResult `json:"general"`
|
||||
Interface *ConfigInterfaceResult `json:"interface"`
|
||||
}
|
||||
|
||||
type FindFilterType struct {
|
||||
Q *string `json:"q"`
|
||||
Page *int `json:"page"`
|
||||
PerPage *int `json:"per_page"`
|
||||
Sort *string `json:"sort"`
|
||||
Direction *SortDirectionEnum `json:"direction"`
|
||||
}
|
||||
|
||||
type FindGalleriesResultType struct {
|
||||
Count int `json:"count"`
|
||||
Galleries []*Gallery `json:"galleries"`
|
||||
}
|
||||
|
||||
type FindPerformersResultType struct {
|
||||
Count int `json:"count"`
|
||||
Performers []*Performer `json:"performers"`
|
||||
}
|
||||
|
||||
type FindSceneMarkersResultType struct {
|
||||
Count int `json:"count"`
|
||||
SceneMarkers []*SceneMarker `json:"scene_markers"`
|
||||
}
|
||||
|
||||
type FindScenesResultType struct {
|
||||
Count int `json:"count"`
|
||||
Scenes []*Scene `json:"scenes"`
|
||||
}
|
||||
|
||||
type FindStudiosResultType struct {
|
||||
Count int `json:"count"`
|
||||
Studios []*Studio `json:"studios"`
|
||||
}
|
||||
|
||||
type GalleryFilesType struct {
|
||||
Index int `json:"index"`
|
||||
Name *string `json:"name"`
|
||||
Path *string `json:"path"`
|
||||
}
|
||||
|
||||
type GenerateMetadataInput struct {
|
||||
Sprites bool `json:"sprites"`
|
||||
Previews bool `json:"previews"`
|
||||
Markers bool `json:"markers"`
|
||||
Transcodes bool `json:"transcodes"`
|
||||
}
|
||||
|
||||
type IntCriterionInput struct {
|
||||
Value int `json:"value"`
|
||||
Modifier CriterionModifier `json:"modifier"`
|
||||
}
|
||||
|
||||
type MarkerStringsResultType struct {
|
||||
Count int `json:"count"`
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
type PerformerCreateInput struct {
|
||||
Name *string `json:"name"`
|
||||
URL *string `json:"url"`
|
||||
Birthdate *string `json:"birthdate"`
|
||||
Ethnicity *string `json:"ethnicity"`
|
||||
Country *string `json:"country"`
|
||||
EyeColor *string `json:"eye_color"`
|
||||
Height *string `json:"height"`
|
||||
Measurements *string `json:"measurements"`
|
||||
FakeTits *string `json:"fake_tits"`
|
||||
CareerLength *string `json:"career_length"`
|
||||
Tattoos *string `json:"tattoos"`
|
||||
Piercings *string `json:"piercings"`
|
||||
Aliases *string `json:"aliases"`
|
||||
Twitter *string `json:"twitter"`
|
||||
Instagram *string `json:"instagram"`
|
||||
Favorite *bool `json:"favorite"`
|
||||
// This should be base64 encoded
|
||||
Image string `json:"image"`
|
||||
}
|
||||
|
||||
type PerformerDestroyInput struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type PerformerFilterType struct {
|
||||
// Filter by favorite
|
||||
FilterFavorites *bool `json:"filter_favorites"`
|
||||
}
|
||||
|
||||
type PerformerUpdateInput struct {
|
||||
ID string `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
URL *string `json:"url"`
|
||||
Birthdate *string `json:"birthdate"`
|
||||
Ethnicity *string `json:"ethnicity"`
|
||||
Country *string `json:"country"`
|
||||
EyeColor *string `json:"eye_color"`
|
||||
Height *string `json:"height"`
|
||||
Measurements *string `json:"measurements"`
|
||||
FakeTits *string `json:"fake_tits"`
|
||||
CareerLength *string `json:"career_length"`
|
||||
Tattoos *string `json:"tattoos"`
|
||||
Piercings *string `json:"piercings"`
|
||||
Aliases *string `json:"aliases"`
|
||||
Twitter *string `json:"twitter"`
|
||||
Instagram *string `json:"instagram"`
|
||||
Favorite *bool `json:"favorite"`
|
||||
// This should be base64 encoded
|
||||
Image *string `json:"image"`
|
||||
}
|
||||
|
||||
type ScanMetadataInput struct {
|
||||
NameFromMetadata bool `json:"nameFromMetadata"`
|
||||
}
|
||||
|
||||
type SceneFileType struct {
|
||||
Size *string `json:"size"`
|
||||
Duration *float64 `json:"duration"`
|
||||
VideoCodec *string `json:"video_codec"`
|
||||
AudioCodec *string `json:"audio_codec"`
|
||||
Width *int `json:"width"`
|
||||
Height *int `json:"height"`
|
||||
Framerate *float64 `json:"framerate"`
|
||||
Bitrate *int `json:"bitrate"`
|
||||
}
|
||||
|
||||
type SceneFilterType struct {
|
||||
// Filter by rating
|
||||
Rating *IntCriterionInput `json:"rating"`
|
||||
// Filter by resolution
|
||||
Resolution *ResolutionEnum `json:"resolution"`
|
||||
// Filter to only include scenes which have markers. `true` or `false`
|
||||
HasMarkers *string `json:"has_markers"`
|
||||
// Filter to only include scenes missing this property
|
||||
IsMissing *string `json:"is_missing"`
|
||||
// Filter to only include scenes with this studio
|
||||
StudioID *string `json:"studio_id"`
|
||||
// Filter to only include scenes with these tags
|
||||
Tags []string `json:"tags"`
|
||||
// Filter to only include scenes with this performer
|
||||
PerformerID *string `json:"performer_id"`
|
||||
}
|
||||
|
||||
type SceneMarkerCreateInput struct {
|
||||
Title string `json:"title"`
|
||||
Seconds float64 `json:"seconds"`
|
||||
SceneID string `json:"scene_id"`
|
||||
PrimaryTagID string `json:"primary_tag_id"`
|
||||
TagIds []string `json:"tag_ids"`
|
||||
}
|
||||
|
||||
type SceneMarkerFilterType struct {
|
||||
// Filter to only include scene markers with this tag
|
||||
TagID *string `json:"tag_id"`
|
||||
// Filter to only include scene markers with these tags
|
||||
Tags []string `json:"tags"`
|
||||
// Filter to only include scene markers attached to a scene with these tags
|
||||
SceneTags []string `json:"scene_tags"`
|
||||
// Filter to only include scene markers with these performers
|
||||
Performers []string `json:"performers"`
|
||||
}
|
||||
|
||||
type SceneMarkerTag struct {
|
||||
Tag *Tag `json:"tag"`
|
||||
SceneMarkers []*SceneMarker `json:"scene_markers"`
|
||||
}
|
||||
|
||||
type SceneMarkerUpdateInput struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Seconds float64 `json:"seconds"`
|
||||
SceneID string `json:"scene_id"`
|
||||
PrimaryTagID string `json:"primary_tag_id"`
|
||||
TagIds []string `json:"tag_ids"`
|
||||
}
|
||||
|
||||
type ScenePathsType struct {
|
||||
Screenshot *string `json:"screenshot"`
|
||||
Preview *string `json:"preview"`
|
||||
Stream *string `json:"stream"`
|
||||
Webp *string `json:"webp"`
|
||||
Vtt *string `json:"vtt"`
|
||||
ChaptersVtt *string `json:"chapters_vtt"`
|
||||
}
|
||||
|
||||
type SceneUpdateInput struct {
|
||||
ClientMutationID *string `json:"clientMutationId"`
|
||||
ID string `json:"id"`
|
||||
Title *string `json:"title"`
|
||||
Details *string `json:"details"`
|
||||
URL *string `json:"url"`
|
||||
Date *string `json:"date"`
|
||||
Rating *int `json:"rating"`
|
||||
StudioID *string `json:"studio_id"`
|
||||
GalleryID *string `json:"gallery_id"`
|
||||
PerformerIds []string `json:"performer_ids"`
|
||||
TagIds []string `json:"tag_ids"`
|
||||
}
|
||||
|
||||
// A performer from a scraping operation...
|
||||
type ScrapedPerformer struct {
|
||||
Name *string `json:"name"`
|
||||
URL *string `json:"url"`
|
||||
Twitter *string `json:"twitter"`
|
||||
Instagram *string `json:"instagram"`
|
||||
Birthdate *string `json:"birthdate"`
|
||||
Ethnicity *string `json:"ethnicity"`
|
||||
Country *string `json:"country"`
|
||||
EyeColor *string `json:"eye_color"`
|
||||
Height *string `json:"height"`
|
||||
Measurements *string `json:"measurements"`
|
||||
FakeTits *string `json:"fake_tits"`
|
||||
CareerLength *string `json:"career_length"`
|
||||
Tattoos *string `json:"tattoos"`
|
||||
Piercings *string `json:"piercings"`
|
||||
Aliases *string `json:"aliases"`
|
||||
}
|
||||
|
||||
type StatsResultType struct {
|
||||
SceneCount int `json:"scene_count"`
|
||||
GalleryCount int `json:"gallery_count"`
|
||||
PerformerCount int `json:"performer_count"`
|
||||
StudioCount int `json:"studio_count"`
|
||||
TagCount int `json:"tag_count"`
|
||||
}
|
||||
|
||||
type StudioCreateInput struct {
|
||||
Name string `json:"name"`
|
||||
URL *string `json:"url"`
|
||||
// This should be base64 encoded
|
||||
Image string `json:"image"`
|
||||
}
|
||||
|
||||
type StudioDestroyInput struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type StudioUpdateInput struct {
|
||||
ID string `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
URL *string `json:"url"`
|
||||
// This should be base64 encoded
|
||||
Image *string `json:"image"`
|
||||
}
|
||||
|
||||
type TagCreateInput struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type TagDestroyInput struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type TagUpdateInput struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type CriterionModifier string
|
||||
|
||||
const (
|
||||
// =
|
||||
CriterionModifierEquals CriterionModifier = "EQUALS"
|
||||
// !=
|
||||
CriterionModifierNotEquals CriterionModifier = "NOT_EQUALS"
|
||||
// >
|
||||
CriterionModifierGreaterThan CriterionModifier = "GREATER_THAN"
|
||||
// <
|
||||
CriterionModifierLessThan CriterionModifier = "LESS_THAN"
|
||||
// IS NULL
|
||||
CriterionModifierIsNull CriterionModifier = "IS_NULL"
|
||||
// IS NOT NULL
|
||||
CriterionModifierNotNull CriterionModifier = "NOT_NULL"
|
||||
CriterionModifierIncludes CriterionModifier = "INCLUDES"
|
||||
CriterionModifierExcludes CriterionModifier = "EXCLUDES"
|
||||
)
|
||||
|
||||
var AllCriterionModifier = []CriterionModifier{
|
||||
CriterionModifierEquals,
|
||||
CriterionModifierNotEquals,
|
||||
CriterionModifierGreaterThan,
|
||||
CriterionModifierLessThan,
|
||||
CriterionModifierIsNull,
|
||||
CriterionModifierNotNull,
|
||||
CriterionModifierIncludes,
|
||||
CriterionModifierExcludes,
|
||||
}
|
||||
|
||||
func (e CriterionModifier) IsValid() bool {
|
||||
switch e {
|
||||
case CriterionModifierEquals, CriterionModifierNotEquals, CriterionModifierGreaterThan, CriterionModifierLessThan, CriterionModifierIsNull, CriterionModifierNotNull, CriterionModifierIncludes, CriterionModifierExcludes:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e CriterionModifier) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *CriterionModifier) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = CriterionModifier(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid CriterionModifier", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e CriterionModifier) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
type ResolutionEnum string
|
||||
|
||||
const (
|
||||
// 240p
|
||||
ResolutionEnumLow ResolutionEnum = "LOW"
|
||||
// 480p
|
||||
ResolutionEnumStandard ResolutionEnum = "STANDARD"
|
||||
// 720p
|
||||
ResolutionEnumStandardHd ResolutionEnum = "STANDARD_HD"
|
||||
// 1080p
|
||||
ResolutionEnumFullHd ResolutionEnum = "FULL_HD"
|
||||
// 4k
|
||||
ResolutionEnumFourK ResolutionEnum = "FOUR_K"
|
||||
)
|
||||
|
||||
var AllResolutionEnum = []ResolutionEnum{
|
||||
ResolutionEnumLow,
|
||||
ResolutionEnumStandard,
|
||||
ResolutionEnumStandardHd,
|
||||
ResolutionEnumFullHd,
|
||||
ResolutionEnumFourK,
|
||||
}
|
||||
|
||||
func (e ResolutionEnum) IsValid() bool {
|
||||
switch e {
|
||||
case ResolutionEnumLow, ResolutionEnumStandard, ResolutionEnumStandardHd, ResolutionEnumFullHd, ResolutionEnumFourK:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e ResolutionEnum) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *ResolutionEnum) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = ResolutionEnum(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid ResolutionEnum", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e ResolutionEnum) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
type SortDirectionEnum string
|
||||
|
||||
const (
|
||||
SortDirectionEnumAsc SortDirectionEnum = "ASC"
|
||||
SortDirectionEnumDesc SortDirectionEnum = "DESC"
|
||||
)
|
||||
|
||||
var AllSortDirectionEnum = []SortDirectionEnum{
|
||||
SortDirectionEnumAsc,
|
||||
SortDirectionEnumDesc,
|
||||
}
|
||||
|
||||
func (e SortDirectionEnum) IsValid() bool {
|
||||
switch e {
|
||||
case SortDirectionEnumAsc, SortDirectionEnumDesc:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e SortDirectionEnum) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *SortDirectionEnum) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = SortDirectionEnum(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid SortDirectionEnum", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e SortDirectionEnum) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import "github.com/99designs/gqlgen/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 StashApp
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,8 +0,0 @@
|
||||
# Stash Frontend V1
|
||||
|
||||
## Dev
|
||||
|
||||
* `yarn install` to install the modules
|
||||
* `yarn start` to start the dev UI server on port 4200
|
||||
* `yarn schema` to regenerate graphql code
|
||||
* `ng build --prod` to build the dist directory
|
||||
@@ -1,105 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"stash-frontend": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"prefix": "app",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"styleext": "scss",
|
||||
"spec": false
|
||||
},
|
||||
"@schematics/angular:class": {
|
||||
"spec": false
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"spec": false
|
||||
},
|
||||
"@schematics/angular:guard": {
|
||||
"spec": false
|
||||
},
|
||||
"@schematics/angular:module": {
|
||||
"spec": false
|
||||
},
|
||||
"@schematics/angular:pipe": {
|
||||
"spec": false
|
||||
},
|
||||
"@schematics/angular:service": {
|
||||
"spec": false
|
||||
}
|
||||
},
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/stash-frontend",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "stash-frontend:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "stash-frontend:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "stash-frontend:build"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"src/tsconfig.app.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "stash-frontend"
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
schema: "../../graphql/schema/**/*.graphql"
|
||||
overwrite: true
|
||||
generates:
|
||||
./../../schema/schema.json:
|
||||
- introspection
|
||||
./src/app/core/graphql-generated.ts:
|
||||
documents: ./../../graphql/documents/**/*.graphql
|
||||
plugins:
|
||||
- add: "/* tslint:disable */"
|
||||
- time
|
||||
- typescript-common
|
||||
- typescript-client
|
||||
- typescript-apollo-angular
|
||||
@@ -1,66 +0,0 @@
|
||||
{
|
||||
"name": "stash-frontend",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e",
|
||||
"dev:server": "ng serve --disableHostCheck --host=0.0.0.0 --port 7001 --ssl true --ssl-cert '../../certs/server.crt' --ssl-key '../../certs/server.key'",
|
||||
"schema": "gql-gen"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "7.2.1",
|
||||
"@angular/common": "7.2.1",
|
||||
"@angular/compiler": "7.2.1",
|
||||
"@angular/core": "7.2.1",
|
||||
"@angular/forms": "7.2.1",
|
||||
"@angular/http": "7.2.1",
|
||||
"@angular/platform-browser": "7.2.1",
|
||||
"@angular/platform-browser-dynamic": "7.2.1",
|
||||
"@angular/router": "7.2.1",
|
||||
"apollo-angular": "1.5.0",
|
||||
"apollo-angular-link-http": "1.4.0",
|
||||
"apollo-cache-inmemory": "1.4.2",
|
||||
"apollo-client": "2.4.12",
|
||||
"apollo-link": "1.2.6",
|
||||
"apollo-link-error": "1.1.5",
|
||||
"apollo-link-ws": "1.0.14",
|
||||
"core-js": "2.6.2",
|
||||
"graphql": "14.1.1",
|
||||
"graphql-code-generator": "0.15.2",
|
||||
"graphql-codegen-add": "0.15.2",
|
||||
"graphql-codegen-introspection": "0.15.2",
|
||||
"graphql-codegen-time": "0.15.2",
|
||||
"graphql-codegen-typescript-apollo-angular": "0.15.2",
|
||||
"graphql-codegen-typescript-client": "0.15.2",
|
||||
"graphql-codegen-typescript-common": "0.15.2",
|
||||
"graphql-codegen-typescript-resolvers": "0.15.2",
|
||||
"graphql-codegen-typescript-server": "0.15.2",
|
||||
"graphql-tag": "2.10.1",
|
||||
"ng-lazyload-image": "5.0.0",
|
||||
"ng2-semantic-ui": "0.9.7",
|
||||
"ngx-clipboard": "11.1.9",
|
||||
"ngx-pagination": "3.2.1",
|
||||
"rxjs": "6.3.3",
|
||||
"rxjs-compat": "6.3.3",
|
||||
"subscriptions-transport-ws": "0.9.15",
|
||||
"zone.js": "0.8.28"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/cli": "7.2.2",
|
||||
"@angular/compiler-cli": "7.2.1",
|
||||
"@angular/language-service": "7.2.1",
|
||||
"@angular-devkit/build-angular": "0.12.2",
|
||||
"@types/node": "10.12.18",
|
||||
"@types/zen-observable": "0.8.0",
|
||||
"codelyzer": "4.5.0",
|
||||
"ts-node": "7.0.1",
|
||||
"tslint": "5.12.1",
|
||||
"typescript": "3.2.4"
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { PageNotFoundComponent } from './core/page-not-found/page-not-found.component';
|
||||
import { DashboardComponent } from './core/dashboard/dashboard.component';
|
||||
|
||||
const appRoutes: Routes = [
|
||||
{ path: '', component: DashboardComponent },
|
||||
{ path: 'scenes', loadChildren: './scenes/scenes.module#ScenesModule' },
|
||||
{ path: 'galleries', loadChildren: './galleries/galleries.module#GalleriesModule' },
|
||||
{ path: 'performers', loadChildren: './performers/performers.module#PerformersModule' },
|
||||
{ path: 'studios', loadChildren: './studios/studios.module#StudiosModule' },
|
||||
{ path: 'tags', loadChildren: './tags/tags.module#TagsModule' },
|
||||
{ path: 'settings', loadChildren: './settings/settings.module#SettingsModule' },
|
||||
{ path: '**', component: PageNotFoundComponent }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forRoot(appRoutes)
|
||||
],
|
||||
exports: [RouterModule],
|
||||
providers: []
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<app-navigation-bar></app-navigation-bar>
|
||||
<div class="ui main container">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class AppComponent {}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
// App
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
// Modules
|
||||
import { CoreModule } from './core/core.module';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
// Only include non-lazy loaded modules here
|
||||
CoreModule,
|
||||
// Keep app routing last so that other module routes install first
|
||||
AppRoutingModule
|
||||
],
|
||||
declarations: [AppComponent],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
// Diagnostic only: inspect router configuration
|
||||
constructor(router: Router) {
|
||||
console.log('Routes: ', JSON.stringify(router.config, undefined, 2));
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { NgModule, Optional, SkipSelf } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { ApolloModule } from 'apollo-angular';
|
||||
import { HttpLinkModule } from 'apollo-angular-link-http';
|
||||
|
||||
import { NavigationBarComponent } from './navigation-bar/navigation-bar.component';
|
||||
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
|
||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||
|
||||
import { StashService } from './stash.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule,
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
ApolloModule,
|
||||
HttpLinkModule
|
||||
],
|
||||
declarations: [
|
||||
NavigationBarComponent,
|
||||
PageNotFoundComponent,
|
||||
DashboardComponent
|
||||
],
|
||||
exports: [
|
||||
NavigationBarComponent,
|
||||
PageNotFoundComponent,
|
||||
DashboardComponent
|
||||
],
|
||||
providers: [
|
||||
StashService
|
||||
]
|
||||
})
|
||||
export class CoreModule {
|
||||
constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
|
||||
if (parentModule) {
|
||||
throw new Error('CoreModule is already loaded. Import it in the AppModule only');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
<div class="ui inverted center aligned stackable vertically divided grid">
|
||||
<div class="one column row">
|
||||
<div class="ui inverted statistics column">
|
||||
<div class="statistic">
|
||||
<div class="value">
|
||||
{{stats?.scene_count}}
|
||||
</div>
|
||||
<div class="label">
|
||||
Scenes
|
||||
</div>
|
||||
</div>
|
||||
<div class="statistic">
|
||||
<div class="value">
|
||||
{{stats?.gallery_count}}
|
||||
</div>
|
||||
<div class="label">
|
||||
Galleries
|
||||
</div>
|
||||
</div>
|
||||
<div class="statistic">
|
||||
<div class="value">
|
||||
{{stats?.performer_count}}
|
||||
</div>
|
||||
<div class="label">
|
||||
Performers
|
||||
</div>
|
||||
</div>
|
||||
<div class="statistic">
|
||||
<div class="value">
|
||||
{{stats?.studio_count}}
|
||||
</div>
|
||||
<div class="label">
|
||||
Studios
|
||||
</div>
|
||||
</div>
|
||||
<div class="statistic">
|
||||
<div class="value">
|
||||
{{stats?.tag_count}}
|
||||
</div>
|
||||
<div class="label">
|
||||
Tags
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="one column row">
|
||||
<div class="column">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { StashService } from '../stash.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
templateUrl: './dashboard.component.html',
|
||||
styleUrls: ['./dashboard.component.css']
|
||||
})
|
||||
export class DashboardComponent implements OnInit {
|
||||
stats: any;
|
||||
|
||||
constructor(private stashService: StashService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.fetchStats();
|
||||
}
|
||||
|
||||
async fetchStats() {
|
||||
const result = await this.stashService.stats().result();
|
||||
this.stats = result.data.stats;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,38 +0,0 @@
|
||||
<div class="ui inverted top fixed menu">
|
||||
<div class="ui container">
|
||||
<div class="header item">
|
||||
<a routerLink="/">Stash</a>
|
||||
</div>
|
||||
<a routerLink="/scenes" routerLinkActive #rla="routerLinkActive" [class.active]="isScenesActiveHack(rla)" class="item">
|
||||
<i class="video play icon"></i>Scenes
|
||||
</a>
|
||||
<a routerLink="/scenes/markers" routerLinkActive="active" class="item">
|
||||
<i class="marker icon"></i>
|
||||
Markers
|
||||
</a>
|
||||
<a routerLink="/galleries" routerLinkActive="active" class="item">
|
||||
<i class="image icon"></i>
|
||||
Galleries
|
||||
</a>
|
||||
<a routerLink="/performers" routerLinkActive="active" class="item">
|
||||
<i class="user icon"></i>
|
||||
Performers
|
||||
</a>
|
||||
<a routerLink="/studios" routerLinkActive="active" class="item">
|
||||
<i class="record icon"></i>
|
||||
Studios
|
||||
</a>
|
||||
<a routerLink="/tags" routerLinkActive="active" class="item">
|
||||
<i class="tags icon"></i>
|
||||
Tags
|
||||
</a>
|
||||
<a routerLink="/scenes/wall" routerLinkActive="active" class="item">
|
||||
<i class="film icon"></i>
|
||||
Scene Wall
|
||||
</a>
|
||||
<a routerLink="/settings" routerLinkActive="active" class="item">
|
||||
<i class="settings icon"></i>
|
||||
Settings
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-navigation-bar',
|
||||
templateUrl: './navigation-bar.component.html',
|
||||
styleUrls: ['./navigation-bar.component.css']
|
||||
})
|
||||
export class NavigationBarComponent implements OnInit {
|
||||
|
||||
constructor(private router: Router) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
isScenesActiveHack(rla) {
|
||||
return rla.isActive &&
|
||||
this.router.url !== '/scenes/wall' &&
|
||||
!this.router.url.includes('/scenes/markers');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<h1>Page not found</h1>
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-page-not-found',
|
||||
templateUrl: './page-not-found.component.html',
|
||||
styleUrls: ['./page-not-found.component.css']
|
||||
})
|
||||
export class PageNotFoundComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,508 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { PlatformLocation } from '@angular/common';
|
||||
|
||||
import { ListFilter } from '../shared/models/list-state.model';
|
||||
|
||||
import { Apollo, QueryRef } from 'apollo-angular';
|
||||
import { HttpLink } from 'apollo-angular-link-http';
|
||||
import { InMemoryCache } from 'apollo-cache-inmemory';
|
||||
import { onError } from 'apollo-link-error';
|
||||
import { ApolloLink } from 'apollo-link';
|
||||
import { getMainDefinition } from 'apollo-utilities';
|
||||
|
||||
import * as GQL from './graphql-generated';
|
||||
import {WebSocketLink} from "apollo-link-ws";
|
||||
|
||||
@Injectable()
|
||||
export class StashService {
|
||||
private findScenesGQL = new GQL.FindScenesGQL(this.apollo);
|
||||
private findSceneGQL = new GQL.FindSceneGQL(this.apollo);
|
||||
private findSceneForEditingGQL = new GQL.FindSceneForEditingGQL(this.apollo);
|
||||
private findSceneMarkersGQL = new GQL.FindSceneMarkersGQL(this.apollo);
|
||||
private sceneWallGQL = new GQL.SceneWallGQL(this.apollo);
|
||||
private markerWallGQL = new GQL.MarkerWallGQL(this.apollo);
|
||||
private findPerformersGQL = new GQL.FindPerformersGQL(this.apollo);
|
||||
private findPerformerGQL = new GQL.FindPerformerGQL(this.apollo);
|
||||
private findStudiosGQL = new GQL.FindStudiosGQL(this.apollo);
|
||||
private findStudioGQL = new GQL.FindStudioGQL(this.apollo);
|
||||
private findGalleriesGQL = new GQL.FindGalleriesGQL(this.apollo);
|
||||
private findGalleryGQL = new GQL.FindGalleryGQL(this.apollo);
|
||||
private findTagGQL = new GQL.FindTagGQL(this.apollo);
|
||||
private markerStringsGQL = new GQL.MarkerStringsGQL(this.apollo);
|
||||
private scrapeFreeonesGQL = new GQL.ScrapeFreeonesGQL(this.apollo);
|
||||
private scrapeFreeonesPerformersGQL = new GQL.ScrapeFreeonesPerformersGQL(this.apollo);
|
||||
private allTagsGQL = new GQL.AllTagsGQL(this.apollo);
|
||||
private allTagsForFilterGQL = new GQL.AllTagsForFilterGQL(this.apollo);
|
||||
private allPerformersForFilterGQL = new GQL.AllPerformersForFilterGQL(this.apollo);
|
||||
private statsGQL = new GQL.StatsGQL(this.apollo);
|
||||
private sceneUpdateGQL = new GQL.SceneUpdateGQL(this.apollo);
|
||||
private performerCreateGQL = new GQL.PerformerCreateGQL(this.apollo);
|
||||
private performerUpdateGQL = new GQL.PerformerUpdateGQL(this.apollo);
|
||||
private studioCreateGQL = new GQL.StudioCreateGQL(this.apollo);
|
||||
private studioUpdateGQL = new GQL.StudioUpdateGQL(this.apollo);
|
||||
private tagCreateGQL = new GQL.TagCreateGQL(this.apollo);
|
||||
private tagDestroyGQL = new GQL.TagDestroyGQL(this.apollo);
|
||||
private tagUpdateGQL = new GQL.TagUpdateGQL(this.apollo);
|
||||
private sceneMarkerCreateGQL = new GQL.SceneMarkerCreateGQL(this.apollo);
|
||||
private sceneMarkerUpdateGQL = new GQL.SceneMarkerUpdateGQL(this.apollo);
|
||||
private sceneMarkerDestroyGQL = new GQL.SceneMarkerDestroyGQL(this.apollo);
|
||||
private metadataImportGQL = new GQL.MetadataImportGQL(this.apollo);
|
||||
private metadataExportGQL = new GQL.MetadataExportGQL(this.apollo);
|
||||
private metadataScanGQL = new GQL.MetadataScanGQL(this.apollo);
|
||||
private metadataGenerateGQL = new GQL.MetadataGenerateGQL(this.apollo);
|
||||
private metadataCleanGQL = new GQL.MetadataCleanGQL(this.apollo);
|
||||
private metadataUpdateGQL = new GQL.MetadataUpdateGQL(this.apollo);
|
||||
|
||||
constructor(private apollo: Apollo, private platformLocation: PlatformLocation, private httpLink: HttpLink) {
|
||||
const platform: any = platformLocation;
|
||||
const platformUrl = new URL(platform.location.origin);
|
||||
platformUrl.port = platformUrl.protocol === 'https:' ? '9999' : '9998';
|
||||
const url = platformUrl.toString().slice(0, -1);
|
||||
const webSocketScheme = platformUrl.protocol === 'https:' ? 'wss' : 'ws';
|
||||
|
||||
const wsLink = new WebSocketLink({
|
||||
uri: `${webSocketScheme}://${platform.location.hostname}:${platformUrl.port}/graphql`,
|
||||
options: {
|
||||
reconnect: true
|
||||
}
|
||||
});
|
||||
|
||||
const errorLink = onError(({ graphQLErrors, networkError }) => {
|
||||
if (graphQLErrors) {
|
||||
graphQLErrors.map(({ message, locations, path }) =>
|
||||
console.log(
|
||||
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (networkError) {
|
||||
console.log(`[Network error]: ${networkError}`);
|
||||
}
|
||||
});
|
||||
|
||||
const httpLinkHandler = httpLink.create({uri: `${url}/graphql`});
|
||||
|
||||
const splitLink = ApolloLink.split(
|
||||
// split based on operation type
|
||||
({ query }) => {
|
||||
const definition = getMainDefinition(query);
|
||||
return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
|
||||
},
|
||||
wsLink,
|
||||
httpLinkHandler
|
||||
);
|
||||
|
||||
const link = ApolloLink.from([
|
||||
errorLink,
|
||||
splitLink
|
||||
]);
|
||||
|
||||
apollo.create({
|
||||
link: link,
|
||||
defaultOptions: {
|
||||
watchQuery: {
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'all'
|
||||
},
|
||||
},
|
||||
cache: new InMemoryCache({
|
||||
// dataIdFromObject: o => {
|
||||
// if (o.__typename === "MarkerStringsResultType") {
|
||||
// return `${o.__typename}:${o.title}`
|
||||
// } else {
|
||||
// return `${o.__typename}:${o.id}`
|
||||
// }
|
||||
// },
|
||||
|
||||
cacheRedirects: {
|
||||
Query: {
|
||||
findScene: (rootValue, args, context) => {
|
||||
return context.getCacheKey({__typename: 'Scene', id: args.id});
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
findScenes(page?: number, filter?: ListFilter): QueryRef<GQL.FindScenes.Query, Record<string, any>> {
|
||||
let scene_filter = {};
|
||||
if (filter.criteriaFilterOpen) {
|
||||
scene_filter = filter.makeSceneFilter();
|
||||
}
|
||||
if (filter.customCriteria) {
|
||||
filter.customCriteria.forEach(criteria => {
|
||||
scene_filter[criteria.key] = criteria.value;
|
||||
});
|
||||
}
|
||||
|
||||
return this.findScenesGQL.watch({
|
||||
filter: {
|
||||
q: filter.searchTerm,
|
||||
page: page,
|
||||
per_page: filter.itemsPerPage,
|
||||
sort: filter.sortBy,
|
||||
direction: filter.sortDirection === 'asc' ? GQL.SortDirectionEnum.Asc : GQL.SortDirectionEnum.Desc
|
||||
},
|
||||
scene_filter: scene_filter
|
||||
});
|
||||
}
|
||||
|
||||
findScene(id?: any, checksum?: string) {
|
||||
return this.findSceneGQL.watch({
|
||||
id: id,
|
||||
checksum: checksum
|
||||
});
|
||||
}
|
||||
|
||||
findSceneForEditing(id?: any) {
|
||||
return this.findSceneForEditingGQL.watch({
|
||||
id: id
|
||||
});
|
||||
}
|
||||
|
||||
findSceneMarkers(page?: number, filter?: ListFilter) {
|
||||
let scene_marker_filter = {};
|
||||
if (filter.criteriaFilterOpen) {
|
||||
scene_marker_filter = filter.makeSceneMarkerFilter();
|
||||
}
|
||||
if (filter.customCriteria) {
|
||||
filter.customCriteria.forEach(criteria => {
|
||||
scene_marker_filter[criteria.key] = criteria.value;
|
||||
});
|
||||
}
|
||||
|
||||
return this.findSceneMarkersGQL.watch({
|
||||
filter: {
|
||||
q: filter.searchTerm,
|
||||
page: page,
|
||||
per_page: filter.itemsPerPage,
|
||||
sort: filter.sortBy,
|
||||
direction: filter.sortDirection === 'asc' ? GQL.SortDirectionEnum.Asc : GQL.SortDirectionEnum.Desc
|
||||
},
|
||||
scene_marker_filter: scene_marker_filter
|
||||
});
|
||||
}
|
||||
|
||||
sceneWall(q?: string) {
|
||||
return this.sceneWallGQL.watch({
|
||||
q: q
|
||||
},
|
||||
{
|
||||
fetchPolicy: 'network-only'
|
||||
});
|
||||
}
|
||||
|
||||
markerWall(q?: string) {
|
||||
return this.markerWallGQL.watch({
|
||||
q: q
|
||||
},
|
||||
{
|
||||
fetchPolicy: 'network-only'
|
||||
});
|
||||
}
|
||||
|
||||
findPerformers(page?: number, filter?: ListFilter) {
|
||||
let performer_filter = {};
|
||||
if (filter.criteriaFilterOpen) {
|
||||
performer_filter = filter.makePerformerFilter();
|
||||
}
|
||||
// if (filter.customCriteria) {
|
||||
// filter.customCriteria.forEach(criteria => {
|
||||
// scene_filter[criteria.key] = criteria.value;
|
||||
// });
|
||||
// }
|
||||
|
||||
return this.findPerformersGQL.watch({
|
||||
filter: {
|
||||
q: filter.searchTerm,
|
||||
page: page,
|
||||
per_page: filter.itemsPerPage,
|
||||
sort: filter.sortBy,
|
||||
direction: filter.sortDirection === 'asc' ? GQL.SortDirectionEnum.Asc : GQL.SortDirectionEnum.Desc
|
||||
},
|
||||
performer_filter: performer_filter
|
||||
});
|
||||
}
|
||||
|
||||
findPerformer(id: any) {
|
||||
return this.findPerformerGQL.watch({
|
||||
id: id
|
||||
});
|
||||
}
|
||||
|
||||
findStudios(page?: number, filter?: ListFilter) {
|
||||
return this.findStudiosGQL.watch({
|
||||
filter: {
|
||||
q: filter.searchTerm,
|
||||
page: page,
|
||||
per_page: filter.itemsPerPage,
|
||||
sort: filter.sortBy,
|
||||
direction: filter.sortDirection === 'asc' ? GQL.SortDirectionEnum.Asc : GQL.SortDirectionEnum.Desc
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
findStudio(id: any) {
|
||||
return this.findStudioGQL.watch({
|
||||
id: id
|
||||
});
|
||||
}
|
||||
|
||||
findGalleries(page?: number, filter?: ListFilter) {
|
||||
return this.findGalleriesGQL.watch({
|
||||
filter: {
|
||||
q: filter.searchTerm,
|
||||
page: page,
|
||||
per_page: filter.itemsPerPage,
|
||||
sort: filter.sortBy,
|
||||
direction: filter.sortDirection === 'asc' ? GQL.SortDirectionEnum.Asc : GQL.SortDirectionEnum.Desc
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
findGallery(id: any) {
|
||||
return this.findGalleryGQL.watch({
|
||||
id: id
|
||||
});
|
||||
}
|
||||
|
||||
findTag(id: any) {
|
||||
return this.findTagGQL.watch({
|
||||
id: id
|
||||
});
|
||||
}
|
||||
|
||||
markerStrings(q?: string, sort?: string) {
|
||||
return this.markerStringsGQL.watch({
|
||||
q: q,
|
||||
sort: sort
|
||||
});
|
||||
}
|
||||
|
||||
scrapeFreeones(performer_name: string) {
|
||||
return this.scrapeFreeonesGQL.watch({
|
||||
performer_name: performer_name
|
||||
});
|
||||
}
|
||||
|
||||
scrapeFreeonesPerformers(query: string) {
|
||||
return this.scrapeFreeonesPerformersGQL.watch({
|
||||
q: query
|
||||
});
|
||||
}
|
||||
|
||||
allTags() {
|
||||
return this.allTagsGQL.watch();
|
||||
}
|
||||
|
||||
allTagsForFilter() {
|
||||
return this.allTagsForFilterGQL.watch();
|
||||
}
|
||||
|
||||
allPerformersForFilter() {
|
||||
return this.allPerformersForFilterGQL.watch();
|
||||
}
|
||||
|
||||
stats() {
|
||||
return this.statsGQL.watch();
|
||||
}
|
||||
|
||||
sceneUpdate(scene: GQL.SceneUpdate.Variables) {
|
||||
return this.sceneUpdateGQL.mutate({
|
||||
id: scene.id,
|
||||
title: scene.title,
|
||||
details: scene.details,
|
||||
url: scene.url,
|
||||
date: scene.date,
|
||||
rating: scene.rating,
|
||||
studio_id: scene.studio_id,
|
||||
gallery_id: scene.gallery_id,
|
||||
performer_ids: scene.performer_ids,
|
||||
tag_ids: scene.tag_ids
|
||||
},
|
||||
{
|
||||
refetchQueries: [
|
||||
{
|
||||
query: this.findSceneGQL.document,
|
||||
variables: {
|
||||
id: scene.id
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
performerCreate(performer: GQL.PerformerCreate.Variables) {
|
||||
return this.performerCreateGQL.mutate({
|
||||
name: performer.name,
|
||||
url: performer.url,
|
||||
birthdate: performer.birthdate,
|
||||
ethnicity: performer.ethnicity,
|
||||
country: performer.country,
|
||||
eye_color: performer.eye_color,
|
||||
height: performer.height,
|
||||
measurements: performer.measurements,
|
||||
fake_tits: performer.fake_tits,
|
||||
career_length: performer.career_length,
|
||||
tattoos: performer.tattoos,
|
||||
piercings: performer.piercings,
|
||||
aliases: performer.aliases,
|
||||
twitter: performer.twitter,
|
||||
instagram: performer.instagram,
|
||||
favorite: performer.favorite,
|
||||
image: performer.image
|
||||
});
|
||||
}
|
||||
|
||||
performerUpdate(performer: GQL.PerformerUpdate.Variables) {
|
||||
return this.performerUpdateGQL.mutate({
|
||||
id: performer.id,
|
||||
name: performer.name,
|
||||
url: performer.url,
|
||||
birthdate: performer.birthdate,
|
||||
ethnicity: performer.ethnicity,
|
||||
country: performer.country,
|
||||
eye_color: performer.eye_color,
|
||||
height: performer.height,
|
||||
measurements: performer.measurements,
|
||||
fake_tits: performer.fake_tits,
|
||||
career_length: performer.career_length,
|
||||
tattoos: performer.tattoos,
|
||||
piercings: performer.piercings,
|
||||
aliases: performer.aliases,
|
||||
twitter: performer.twitter,
|
||||
instagram: performer.instagram,
|
||||
favorite: performer.favorite,
|
||||
image: performer.image
|
||||
},
|
||||
{
|
||||
refetchQueries: [
|
||||
{
|
||||
query: this.findPerformerGQL.document,
|
||||
variables: {
|
||||
id: performer.id
|
||||
}
|
||||
}
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
studioCreate(studio: GQL.StudioCreate.Variables) {
|
||||
return this.studioCreateGQL.mutate({
|
||||
name: studio.name,
|
||||
url: studio.url,
|
||||
image: studio.image
|
||||
});
|
||||
}
|
||||
|
||||
studioUpdate(studio: GQL.StudioUpdate.Variables) {
|
||||
return this.studioUpdateGQL.mutate({
|
||||
id: studio.id,
|
||||
name: studio.name,
|
||||
url: studio.url,
|
||||
image: studio.image
|
||||
},
|
||||
{
|
||||
refetchQueries: [
|
||||
{
|
||||
query: this.findStudioGQL.document,
|
||||
variables: {
|
||||
id: studio.id
|
||||
}
|
||||
}
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
tagCreate(tag: GQL.TagCreate.Variables) {
|
||||
return this.tagCreateGQL.mutate({
|
||||
name: tag.name
|
||||
});
|
||||
}
|
||||
|
||||
tagDestroy(tag: GQL.TagDestroy.Variables) {
|
||||
return this.tagDestroyGQL.mutate({
|
||||
id: tag.id
|
||||
});
|
||||
}
|
||||
|
||||
tagUpdate(tag: GQL.TagUpdate.Variables) {
|
||||
return this.tagUpdateGQL.mutate({
|
||||
id: tag.id,
|
||||
name: tag.name
|
||||
},
|
||||
{
|
||||
refetchQueries: [
|
||||
{
|
||||
query: this.findTagGQL.document,
|
||||
variables: {
|
||||
id: tag.id
|
||||
}
|
||||
}
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
markerCreate(marker: GQL.SceneMarkerCreate.Variables) {
|
||||
return this.sceneMarkerCreateGQL.mutate({
|
||||
title: marker.title,
|
||||
seconds: marker.seconds,
|
||||
scene_id: marker.scene_id,
|
||||
primary_tag_id: marker.primary_tag_id,
|
||||
tag_ids: marker.tag_ids
|
||||
},
|
||||
{
|
||||
refetchQueries: () => ['FindScene']
|
||||
});
|
||||
}
|
||||
|
||||
markerUpdate(marker: GQL.SceneMarkerUpdate.Variables) {
|
||||
return this.sceneMarkerUpdateGQL.mutate({
|
||||
id: marker.id,
|
||||
title: marker.title,
|
||||
seconds: marker.seconds,
|
||||
scene_id: marker.scene_id,
|
||||
primary_tag_id: marker.primary_tag_id,
|
||||
tag_ids: marker.tag_ids
|
||||
},
|
||||
{
|
||||
refetchQueries: () => ['FindScene']
|
||||
});
|
||||
}
|
||||
|
||||
markerDestroy(id: any, scene_id: any) {
|
||||
return this.sceneMarkerDestroyGQL.mutate({
|
||||
id: id
|
||||
},
|
||||
{
|
||||
refetchQueries: () => ['FindScene']
|
||||
});
|
||||
}
|
||||
|
||||
metadataImport() {
|
||||
return this.metadataImportGQL.watch();
|
||||
}
|
||||
|
||||
metadataExport() {
|
||||
return this.metadataExportGQL.watch();
|
||||
}
|
||||
|
||||
metadataScan() {
|
||||
return this.metadataScanGQL.watch();
|
||||
}
|
||||
|
||||
metadataGenerate() {
|
||||
return this.metadataGenerateGQL.watch();
|
||||
}
|
||||
|
||||
metadataClean() {
|
||||
return this.metadataCleanGQL.watch();
|
||||
}
|
||||
|
||||
metadataUpdate() {
|
||||
return this.metadataUpdateGQL.subscribe()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { GalleriesComponent } from './galleries/galleries.component';
|
||||
import { GalleryListComponent } from './gallery-list/gallery-list.component';
|
||||
import { GalleryDetailComponent } from './gallery-detail/gallery-detail.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '',
|
||||
component: GalleriesComponent,
|
||||
children: [
|
||||
{ path: '', component: GalleryListComponent },
|
||||
{ path: ':id', component: GalleryDetailComponent },
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class GalleriesRoutingModule { }
|
||||
@@ -1,25 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
import { GalleriesRoutingModule } from './galleries-routing.module';
|
||||
import { GalleriesService } from './galleries.service';
|
||||
|
||||
import { GalleriesComponent } from './galleries/galleries.component';
|
||||
import { GalleryDetailComponent } from './gallery-detail/gallery-detail.component';
|
||||
import { GalleryListComponent } from './gallery-list/gallery-list.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
GalleriesRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
GalleriesComponent,
|
||||
GalleryDetailComponent,
|
||||
GalleryListComponent
|
||||
],
|
||||
providers: [
|
||||
GalleriesService
|
||||
]
|
||||
})
|
||||
export class GalleriesModule { }
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { GalleryListState } from '../shared/models/list-state.model';
|
||||
|
||||
@Injectable()
|
||||
export class GalleriesService {
|
||||
listState: GalleryListState = new GalleryListState();
|
||||
|
||||
constructor() { }
|
||||
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-galleries',
|
||||
template: '<router-outlet></router-outlet>'
|
||||
})
|
||||
export class GalleriesComponent implements OnInit {
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
#gallery-modal-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
overflow: hidden;
|
||||
z-index: 1500;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
#gallery-modal-background {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background:rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
#gallery-modal-image-wrapper {
|
||||
position: absolute;
|
||||
transform-origin: left top;
|
||||
transition: transform 333ms cubic-bezier(.4,0,.22,1);
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
#gallery-modal-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
padding: 20px;
|
||||
background: rgba(17, 17, 17, 0.5);
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<div
|
||||
*ngIf="!!displayedImage"
|
||||
(window:keydown)="onKey($event)"
|
||||
id="gallery-modal-container">
|
||||
<div id="gallery-modal-background"></div>
|
||||
<div *ngIf="!!displayedImage.path" id="gallery-modal-image-wrapper">
|
||||
<img id="gallery-modal-image" [src]="displayedImage.path" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui text menu">
|
||||
<h3 class="header item">{{gallery?.title || 'No Title'}} - {{gallery?.files.length}} files</h3>
|
||||
<div class="right menu">
|
||||
<button (click)="onClickEdit()" class="ui button">Edit</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-gallery-preview
|
||||
[gallery]="gallery"
|
||||
[type]="'full'"
|
||||
(clicked)="onClickCard($event)">
|
||||
</app-gallery-preview>
|
||||
@@ -1,92 +0,0 @@
|
||||
import { Component, OnInit, HostListener } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { StashService } from '../../core/stash.service';
|
||||
|
||||
import { GalleryImage } from '../../shared/models/gallery.model';
|
||||
import { GalleryData } from '../../core/graphql-generated';
|
||||
|
||||
@Component({
|
||||
selector: 'app-gallery-detail',
|
||||
templateUrl: './gallery-detail.component.html',
|
||||
styleUrls: ['./gallery-detail.component.css']
|
||||
})
|
||||
export class GalleryDetailComponent implements OnInit {
|
||||
gallery: GalleryData.Fragment;
|
||||
displayedImage: GalleryImage = null;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private stashService: StashService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.getGallery();
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
async getGallery() {
|
||||
const id = parseInt(this.route.snapshot.params['id'], 10);
|
||||
const result = await this.stashService.findGallery(id).result();
|
||||
this.gallery = result.data.findGallery;
|
||||
}
|
||||
|
||||
@HostListener('mousewheel', ['$event'])
|
||||
onMousewheel(event) {
|
||||
this.displayedImage = null;
|
||||
}
|
||||
|
||||
@HostListener('window:mouseup', ['$event'])
|
||||
onMouseup(event: MouseEvent) {
|
||||
if (event.button !== 0 || !(event.target instanceof HTMLDivElement)) { return; }
|
||||
const target: HTMLDivElement = event.target;
|
||||
if (target.id !== 'gallery-image') {
|
||||
this.displayedImage = null;
|
||||
} else {
|
||||
window.open(this.displayedImage.path, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
onClickEdit() {
|
||||
// TODO
|
||||
console.log('edit');
|
||||
}
|
||||
|
||||
onClickCard(galleryImage: GalleryImage) {
|
||||
console.log(galleryImage);
|
||||
this.displayedImage = galleryImage;
|
||||
}
|
||||
|
||||
onKey(event) {
|
||||
const i = this.displayedImage.index;
|
||||
console.log(event);
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowLeft': {
|
||||
this.displayedImage = this.gallery.files[i - 1];
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ArrowRight': {
|
||||
this.displayedImage = this.gallery.files[i + 1];
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ArrowUp': {
|
||||
window.open(this.displayedImage.path, '_blank');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ArrowDown': {
|
||||
this.displayedImage = null;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<div class="ui text menu">
|
||||
<div class="right menu">
|
||||
<button (click)="onClickNew()" class="ui button">New</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-list [state]="state"></app-list>
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { GalleriesService } from '../galleries.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-gallery-list',
|
||||
templateUrl: './gallery-list.component.html'
|
||||
})
|
||||
export class GalleryListComponent implements OnInit {
|
||||
state = this.galleriesService.listState;
|
||||
|
||||
constructor(private galleriesService: GalleriesService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
onClickNew() {
|
||||
this.router.navigate(['new'], { relativeTo: this.route });
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
<div class="ui three column grid" style="position: fixed; filter: blur(4px); z-index: -1; transform: translateZ(0); left: calc(-50vw + 50.7%); width: 100vw;">
|
||||
<div *ngFor="let scene of sceneListState.data | shuffle | slice:0:6" class="column" style="background-size: cover; background-position: center center; height: 100vh;" [style.background-image]="'url(' + scene.paths.screenshot + ')'">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background-color: rgba(255, 255, 255, 0.7); margin-top: -1.8em;">
|
||||
<div class="ui basic segment">
|
||||
<div class="ui two column divided middle aligned very relaxed stackable grid" style="position: relative;">
|
||||
<div class="column">
|
||||
<img *ngIf="!!performer" [src]="performer?.image_path" class="ui fluid bordered image" />
|
||||
</div>
|
||||
<div class="center aligned column">
|
||||
<div class="ui huge very relaxed list items">
|
||||
<div class="item">
|
||||
<div class="content">
|
||||
<div class="header">
|
||||
{{performer?.name}}
|
||||
<button (click)="onClickEdit()" class="ui right floated button">Edit</button>
|
||||
</div>
|
||||
<div *ngIf="!!performer?.aliases" class="meta">{{performer.aliases}}</div>
|
||||
<div *ngIf="!!performer?.birthdate" class="extra">
|
||||
{{performer?.birthdate | date:"MM/dd/yy"}} - Age {{performer?.birthdate | age}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="content">
|
||||
<div class="header">Details</div>
|
||||
<div class="description">
|
||||
<div class="ui mini list">
|
||||
<div *ngIf="!!performer?.career_length" class="item">
|
||||
<span class="bold">Career Length</span>
|
||||
{{performer.career_length}}
|
||||
</div>
|
||||
<div *ngIf="!!performer?.country" class="item">
|
||||
<span class="bold">Country</span>
|
||||
{{performer.country}}
|
||||
</div>
|
||||
<div *ngIf="!!performer?.ethnicity" class="item">
|
||||
<span class="bold">Ethnicity</span>
|
||||
{{performer.ethnicity?.toUpperCase()}}
|
||||
</div>
|
||||
<div *ngIf="!!performer?.eye_color" class="item">
|
||||
<span class="bold">Eye Color</span>
|
||||
{{performer.eye_color}}
|
||||
</div>
|
||||
<div *ngIf="!!performer?.height" class="item">
|
||||
<span class="bold">Height (cm)</span>
|
||||
{{performer.height}}
|
||||
</div>
|
||||
<div *ngIf="!!performer?.measurements" class="item">
|
||||
<span class="bold">Measurements</span>
|
||||
{{performer.measurements}}
|
||||
</div>
|
||||
<div *ngIf="!!performer?.fake_tits" class="item">
|
||||
<span class="bold">Fake Tits</span>
|
||||
{{performer.fake_tits}}
|
||||
</div>
|
||||
<div *ngIf="!!performer?.tattoos" class="item">
|
||||
<span class="bold">Tattoos</span>
|
||||
{{performer.tattoos}}
|
||||
</div>
|
||||
<div *ngIf="!!performer?.piercings" class="item">
|
||||
<span class="bold">Piercings</span>
|
||||
{{performer.piercings}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="performer?.twitter?.length > 0 || performer?.instagram?.length > 0" class="ui basic segment">
|
||||
<h3>Social</h3>
|
||||
<div class="ui horizontal list">
|
||||
<div *ngIf="!!performer?.twitter && performer?.twitter?.length > 0" class="item">
|
||||
<i class="big twitter icon"></i>
|
||||
<div class="content">
|
||||
<a [href]="twitterLink()">{{performer.twitter}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!!performer?.instagram && performer?.instagram?.length > 0" class="item">
|
||||
<i class="big instagram icon"></i>
|
||||
<div class="content">
|
||||
<a [href]="instagramLink()">{{performer.instagram}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui basic segment">
|
||||
<div class="ui list">
|
||||
<a *ngIf="!!performer?.url" [href]="performer?.url" class="item" title="More information">
|
||||
<i class="linkify icon"></i>
|
||||
<div class="content">
|
||||
<div class="header">URL</div>
|
||||
<div class="description">{{performer?.url}}</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui fluid container" style="
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
background-color: #000;
|
||||
padding: 2rem;
|
||||
box-shadow: 0px 0px 5px 0px rgba(34, 36, 38, 0.40);
|
||||
">
|
||||
<div class="ui container">
|
||||
<h1 class="header">
|
||||
{{performer?.name || 'Performer'}}'s Scenes
|
||||
</h1>
|
||||
|
||||
<app-list [state]="sceneListState"></app-list>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,56 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { StashService } from '../../core/stash.service';
|
||||
import { PerformersService } from '../performers.service';
|
||||
|
||||
import { PerformerData } from '../../core/graphql-generated';
|
||||
|
||||
import { SceneListState, CustomCriteria } from '../../shared/models/list-state.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-performer-detail',
|
||||
templateUrl: './performer-detail.component.html',
|
||||
styleUrls: ['./performer-detail.component.css']
|
||||
})
|
||||
export class PerformerDetailComponent implements OnInit {
|
||||
performer: PerformerData.Fragment;
|
||||
sceneListState: SceneListState;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private stashService: StashService,
|
||||
private performerService: PerformersService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
const id = parseInt(this.route.snapshot.params['id'], 10);
|
||||
this.sceneListState = this.performerService.detailsSceneListState;
|
||||
this.sceneListState.filter.customCriteria = [];
|
||||
this.sceneListState.filter.customCriteria.push(new CustomCriteria('performer_id', id.toString()));
|
||||
|
||||
this.getPerformer();
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
getPerformer() {
|
||||
const id = parseInt(this.route.snapshot.params['id'], 10);
|
||||
|
||||
this.stashService.findPerformer(id).valueChanges.subscribe(performer => {
|
||||
this.performer = performer.data.findPerformer;
|
||||
});
|
||||
}
|
||||
|
||||
onClickEdit() {
|
||||
this.router.navigate(['edit'], { relativeTo: this.route });
|
||||
}
|
||||
|
||||
twitterLink(): string {
|
||||
return 'http://www.twitter.com/' + this.performer.twitter;
|
||||
}
|
||||
|
||||
instagramLink(): string {
|
||||
return 'http://www.instagram.com/' + this.performer.instagram;
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
<div [class.loading]="loading" class="ui inverted form">
|
||||
<h4 class="ui inverted dividing header">Performer Information</h4>
|
||||
<div class="field">
|
||||
<div class="fields">
|
||||
<div class="fourteen wide field">
|
||||
<label>Name</label>
|
||||
<div class="ui search focus">
|
||||
<input [formControl]="searchFormControl" type="text" placeholder="Name" />
|
||||
<div *ngIf="performerNameOptions.length > 0 && !selectedName" class="results transition visible">
|
||||
<a *ngFor="let name of performerNameOptions" (click)="onClickedPerformerName(name)" class="result">
|
||||
<div class="content">
|
||||
<div class="title">{{name}}</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="two wide field">
|
||||
<label>Favorite</label>
|
||||
<div (click)="onFavoriteChange()" class="ui massive heart rating">
|
||||
<i class="icon" [class.active]="favorite"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Aliases</label>
|
||||
<input [(ngModel)]="aliases" type="text" placeholder="Aliases" />
|
||||
</div>
|
||||
|
||||
<div class="equal width fields">
|
||||
<div class="field">
|
||||
<label>Origin Country</label>
|
||||
<input [(ngModel)]="country" type="text" placeholder="Origin Country" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Birth Date (YYYY-MM-DD)</label>
|
||||
<input [(ngModel)]="birthdate" type="text" placeholder="YYYY-MM-DD" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Ethnicity</label>
|
||||
<sui-select
|
||||
[(ngModel)]="ethnicity"
|
||||
[options]="ethnicityOptions"
|
||||
placeholder="Ethnicity"
|
||||
#ethnicitySelect>
|
||||
<sui-select-option *ngFor="let option of ethnicitySelect.availableOptions" [value]="option"></sui-select-option>
|
||||
</sui-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="equal width fields">
|
||||
<div class="field">
|
||||
<label>Eye Color</label>
|
||||
<input [(ngModel)]="eye_color" type="text" placeholder="Eye Color" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Height (cm)</label>
|
||||
<input [(ngModel)]="height" type="text" placeholder="Height (cm)" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Measurements</label>
|
||||
<input [(ngModel)]="measurements" type="text" placeholder="Measurements" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Fake Tits</label>
|
||||
<input [(ngModel)]="fake_tits" type="text" placeholder="Fake Tits" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Career Length</label>
|
||||
<input [(ngModel)]="career_length" type="text" placeholder="Career Length" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="equal width fields">
|
||||
<div class="field">
|
||||
<label>Tattoos</label>
|
||||
<input [(ngModel)]="tattoos" type="text" placeholder="Tattoos" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Piercings</label>
|
||||
<input [(ngModel)]="piercings" type="text" placeholder="Piercings" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="equal width fields">
|
||||
<div class="field">
|
||||
<label>URL</label>
|
||||
<input [(ngModel)]="url" type="url" placeholder="URL" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Twitter Handle</label>
|
||||
<input [(ngModel)]="twitter" type="text" placeholder="Twitter Handle" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Instagram Handle</label>
|
||||
<input [(ngModel)]="instagram" type="text" placeholder="Instagram Handle" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Image</label>
|
||||
<div class="fields">
|
||||
<div class="fourteen wide field">
|
||||
<input #imageInput type="file" (change)="onImageChange($event)" placeholder="Upload file" accept=".jpg,.jpeg">
|
||||
</div>
|
||||
<div class="two wide field">
|
||||
<div class="ui small image">
|
||||
<img *ngIf="!!imagePreview" [src]="imagePreview" />
|
||||
</div>
|
||||
<button class="ui button" (click)="onResetImage(imageInput)">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button (click)="onSubmit()" class="ui primary submit button">Submit</button>
|
||||
<button (click)="onScrape()" alt="Type in an exact name and click" class="ui primary submit button">Scrape From Freeones</button>
|
||||
</div>
|
||||
@@ -1,204 +0,0 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { StashService } from '../../core/stash.service';
|
||||
|
||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
import { FormControl } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-performer-form',
|
||||
templateUrl: './performer-form.component.html',
|
||||
styleUrls: ['./performer-form.component.css']
|
||||
})
|
||||
export class PerformerFormComponent implements OnInit, OnDestroy {
|
||||
name: string;
|
||||
favorite: boolean;
|
||||
aliases: string;
|
||||
country: string;
|
||||
birthdate: string;
|
||||
ethnicity: string;
|
||||
eye_color: string;
|
||||
height: string;
|
||||
measurements: string;
|
||||
fake_tits: string;
|
||||
career_length: string;
|
||||
tattoos: string;
|
||||
piercings: string;
|
||||
url: string;
|
||||
twitter: string;
|
||||
instagram: string;
|
||||
image: string;
|
||||
|
||||
loading = true;
|
||||
imagePreview: string;
|
||||
image_path: string;
|
||||
ethnicityOptions: string[] = ['white', 'black', 'asian', 'hispanic'];
|
||||
performerNameOptions: string[] = [];
|
||||
selectedName: string;
|
||||
|
||||
searchFormControl = new FormControl();
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private stashService: StashService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.getPerformer();
|
||||
|
||||
this.searchFormControl.valueChanges.pipe(
|
||||
debounceTime(400),
|
||||
distinctUntilChanged()
|
||||
).subscribe(term => {
|
||||
this.name = term;
|
||||
this.getPerformerNames(term);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {}
|
||||
|
||||
async getPerformer() {
|
||||
const id = parseInt(this.route.snapshot.params['id'], 10);
|
||||
if (!!id === false) {
|
||||
console.log('new performer');
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await this.stashService.findPerformer(id).result();
|
||||
this.loading = result.loading;
|
||||
|
||||
this.name = result.data.findPerformer.name;
|
||||
this.selectedName = this.name;
|
||||
this.searchFormControl.setValue(this.name);
|
||||
this.favorite = result.data.findPerformer.favorite;
|
||||
this.aliases = result.data.findPerformer.aliases;
|
||||
this.country = result.data.findPerformer.country;
|
||||
this.birthdate = result.data.findPerformer.birthdate;
|
||||
this.ethnicity = result.data.findPerformer.ethnicity;
|
||||
this.eye_color = result.data.findPerformer.eye_color;
|
||||
this.height = result.data.findPerformer.height;
|
||||
this.measurements = result.data.findPerformer.measurements;
|
||||
this.fake_tits = result.data.findPerformer.fake_tits;
|
||||
this.career_length = result.data.findPerformer.career_length;
|
||||
this.tattoos = result.data.findPerformer.tattoos;
|
||||
this.piercings = result.data.findPerformer.piercings;
|
||||
this.url = result.data.findPerformer.url;
|
||||
this.twitter = result.data.findPerformer.twitter;
|
||||
this.instagram = result.data.findPerformer.instagram;
|
||||
|
||||
this.image_path = result.data.findPerformer.image_path;
|
||||
this.imagePreview = this.image_path;
|
||||
}
|
||||
|
||||
async getPerformerNames(query: string) {
|
||||
if (query === undefined) { return; }
|
||||
if (this.selectedName !== this.name) { this.selectedName = null; }
|
||||
const result = await this.stashService.scrapeFreeonesPerformers(query).result();
|
||||
this.performerNameOptions = result.data.scrapeFreeonesPerformerList;
|
||||
}
|
||||
|
||||
onClickedPerformerName(name) {
|
||||
this.name = name;
|
||||
this.selectedName = name;
|
||||
this.searchFormControl.setValue(this.name);
|
||||
}
|
||||
|
||||
onImageChange(event) {
|
||||
const file: File = event.target.files[0];
|
||||
const reader: FileReader = new FileReader();
|
||||
|
||||
reader.onloadend = (e) => {
|
||||
this.image = reader.result as string;
|
||||
this.imagePreview = this.image;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
onResetImage(imageInput) {
|
||||
imageInput.value = '';
|
||||
this.imagePreview = this.image_path;
|
||||
this.image = null;
|
||||
}
|
||||
|
||||
onFavoriteChange() {
|
||||
this.favorite = !this.favorite;
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
const id = this.route.snapshot.params['id'];
|
||||
|
||||
if (!!id) {
|
||||
this.stashService.performerUpdate({
|
||||
id: id,
|
||||
name: this.name,
|
||||
url: this.url,
|
||||
birthdate: this.birthdate,
|
||||
ethnicity: this.ethnicity,
|
||||
country: this.country,
|
||||
eye_color: this.eye_color,
|
||||
height: this.height,
|
||||
measurements: this.measurements,
|
||||
fake_tits: this.fake_tits,
|
||||
career_length: this.career_length,
|
||||
tattoos: this.tattoos,
|
||||
piercings: this.piercings,
|
||||
aliases: this.aliases,
|
||||
twitter: this.twitter,
|
||||
instagram: this.instagram,
|
||||
favorite: this.favorite,
|
||||
image: this.image
|
||||
}).subscribe(result => {
|
||||
this.router.navigate(['/performers', id]);
|
||||
});
|
||||
} else {
|
||||
this.stashService.performerCreate({
|
||||
name: this.name,
|
||||
url: this.url,
|
||||
birthdate: this.birthdate,
|
||||
ethnicity: this.ethnicity,
|
||||
country: this.country,
|
||||
eye_color: this.eye_color,
|
||||
height: this.height,
|
||||
measurements: this.measurements,
|
||||
fake_tits: this.fake_tits,
|
||||
career_length: this.career_length,
|
||||
tattoos: this.tattoos,
|
||||
piercings: this.piercings,
|
||||
aliases: this.aliases,
|
||||
twitter: this.twitter,
|
||||
instagram: this.instagram,
|
||||
favorite: this.favorite,
|
||||
image: this.image
|
||||
}).subscribe(result => {
|
||||
this.router.navigate(['/performers', result.data.performerCreate.id]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async onScrape() {
|
||||
this.loading = true;
|
||||
const result = await this.stashService.scrapeFreeones(this.name).result();
|
||||
this.loading = false;
|
||||
|
||||
this.url = result.data.scrapeFreeones.url;
|
||||
this.name = result.data.scrapeFreeones.name;
|
||||
this.searchFormControl.setValue(this.name);
|
||||
this.aliases = result.data.scrapeFreeones.aliases;
|
||||
this.country = result.data.scrapeFreeones.country;
|
||||
this.birthdate = result.data.scrapeFreeones.birthdate ? result.data.scrapeFreeones.birthdate : this.birthdate;
|
||||
this.ethnicity = result.data.scrapeFreeones.ethnicity;
|
||||
this.eye_color = result.data.scrapeFreeones.eye_color;
|
||||
this.height = result.data.scrapeFreeones.height;
|
||||
this.measurements = result.data.scrapeFreeones.measurements;
|
||||
this.fake_tits = result.data.scrapeFreeones.fake_tits;
|
||||
this.career_length = result.data.scrapeFreeones.career_length;
|
||||
this.tattoos = result.data.scrapeFreeones.tattoos;
|
||||
this.piercings = result.data.scrapeFreeones.piercings;
|
||||
this.twitter = result.data.scrapeFreeones.twitter;
|
||||
this.instagram = result.data.scrapeFreeones.instagram;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<div class="ui text menu">
|
||||
<div class="right menu">
|
||||
<button (click)="onClickNew()" class="ui button">New</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-list [state]="state"></app-list>
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { PerformersService } from '../performers.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-performer-list',
|
||||
templateUrl: './performer-list.component.html'
|
||||
})
|
||||
export class PerformerListComponent implements OnInit {
|
||||
state = this.performersService.performerListState;
|
||||
|
||||
constructor(private performersService: PerformersService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
onClickNew() {
|
||||
this.router.navigate(['new'], { relativeTo: this.route });
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { PerformersComponent } from './performers/performers.component';
|
||||
import { PerformerListComponent } from './performer-list/performer-list.component';
|
||||
import { PerformerDetailComponent } from './performer-detail/performer-detail.component';
|
||||
import { PerformerFormComponent } from './performer-form/performer-form.component';
|
||||
|
||||
const performersRoutes: Routes = [
|
||||
{ path: '',
|
||||
component: PerformersComponent,
|
||||
children: [
|
||||
{ path: '', component: PerformerListComponent },
|
||||
{ path: 'new', component: PerformerFormComponent },
|
||||
{ path: ':id', component: PerformerDetailComponent },
|
||||
{ path: ':id/edit', component: PerformerFormComponent }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(performersRoutes)
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class PerformersRoutingModule {}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { PerformersRoutingModule } from './performers-routing.module';
|
||||
import { PerformersService } from './performers.service';
|
||||
|
||||
import { PerformersComponent } from './performers/performers.component';
|
||||
import { PerformerListComponent } from './performer-list/performer-list.component';
|
||||
import { PerformerDetailComponent } from './performer-detail/performer-detail.component';
|
||||
import { PerformerFormComponent } from './performer-form/performer-form.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
SharedModule,
|
||||
PerformersRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
PerformersComponent,
|
||||
PerformerListComponent,
|
||||
PerformerDetailComponent,
|
||||
PerformerFormComponent
|
||||
],
|
||||
providers: [
|
||||
PerformersService
|
||||
]
|
||||
})
|
||||
export class PerformersModule {}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { PerformerListState, SceneListState } from '../shared/models/list-state.model';
|
||||
|
||||
@Injectable()
|
||||
export class PerformersService {
|
||||
performerListState: PerformerListState = new PerformerListState();
|
||||
detailsSceneListState: SceneListState = new SceneListState();
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-performers',
|
||||
template: '<router-outlet></router-outlet>'
|
||||
})
|
||||
export class PerformersComponent implements OnInit {
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { ScenesService } from '../scenes.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-marker-list',
|
||||
template: '<app-list [state]="state"></app-list>'
|
||||
})
|
||||
export class MarkerListComponent implements OnInit {
|
||||
state = this.scenesService.sceneMarkerListState;
|
||||
|
||||
constructor(private scenesService: ScenesService) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
<button style="margin: 5px;" (click)="onClickAddMarker()" class="ui button">Create Marker</button>
|
||||
|
||||
<div [suiCollapse]="!showingMarkerModal">
|
||||
<div class="ui inverted segment">
|
||||
<h4 class="ui header">{{!!editingMarker ? 'Edit' : 'Create'}} Marker</h4>
|
||||
<div class="ui inverted form">
|
||||
<div class="field">
|
||||
<label>Title</label>
|
||||
<div class="ui search focus">
|
||||
<input [formControl]="searchFormControl" (blur)="setHasFocus(false)" (focus)="setHasFocus(true)" type="text" placeholder="Title" />
|
||||
<div *ngIf="filteredMarkerOptions.length > 0 && hasFocus" class="results transition visible">
|
||||
<a *ngFor="let title of filteredMarkerOptions" (click)="onClickMarkerTitle(title)" class="result">
|
||||
<div class="content">
|
||||
<div class="title">{{title}}</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="equal width fields">
|
||||
<div class="field">
|
||||
<label>Seconds</label>
|
||||
<input [(ngModel)]="seconds" type="number" placeholder="Title" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Primary Tag</label>
|
||||
<sui-select
|
||||
class="selection"
|
||||
[(ngModel)]="primary_tag_id"
|
||||
[options]="tags"
|
||||
labelField="name"
|
||||
valueField="id"
|
||||
[isSearchable]="true"
|
||||
placeholder="Primary Tag"
|
||||
#primaryTagSelect>
|
||||
<sui-select-option *ngFor="let option of primaryTagSelect.availableOptions" [value]="option"></sui-select-option>
|
||||
</sui-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Additional Tags</label>
|
||||
<sui-multi-select
|
||||
class="selection"
|
||||
[(ngModel)]="tag_ids"
|
||||
[options]="tags"
|
||||
labelField="name"
|
||||
valueField="id"
|
||||
[isSearchable]="true"
|
||||
placeholder="Tags"
|
||||
#tagSelect>
|
||||
<sui-select-option *ngFor="let option of tagSelect.availableOptions" [value]="option"></sui-select-option>
|
||||
</sui-multi-select>
|
||||
</div>
|
||||
<button (click)="onSubmit()" class="ui primary submit button">Submit</button>
|
||||
<button (click)="onCancel()" class="ui button">Cancel</button>
|
||||
<button *ngIf="!!editingMarker" (click)="onClickDelete()" class="ui right floated negative button">Delete (Click {{3 - deleteClickCount}} times)</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!!scene" class="ui container" style="overflow-y: hidden; overflow-x: auto; white-space: nowrap;">
|
||||
<div *ngFor="let primary_tag of scene.scene_marker_tags" class="ui dark card" style="max-height: 300px; height: 100vh; overflow-y: auto; overflow-x: hidden; display: inline-block; margin: 5px;">
|
||||
<div class="content" style="white-space: normal;">
|
||||
<div class="header">{{primary_tag.tag.name}}</div>
|
||||
<div class="ui divided items">
|
||||
<div *ngFor="let marker of primary_tag.scene_markers" class="item" style="padding: 0.5em 0;">
|
||||
<div class="content">
|
||||
<div class="header" style="font-size: 1em;">
|
||||
<a (click)="onClickMarker(marker)">{{marker.title}}</a>
|
||||
</div>
|
||||
<i (click)="onClickEditMarker(marker)" class="ui right floated link icon edit"></i>
|
||||
<div class="meta">
|
||||
<span>{{marker.seconds | seconds}}</span>
|
||||
</div>
|
||||
<div class="extra">
|
||||
<div class="ui tiny labels">
|
||||
<div *ngFor="let tag of marker.tags" class="ui label">
|
||||
{{tag.name}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,162 +0,0 @@
|
||||
import { Component, OnInit, OnChanges, SimpleChanges, Input } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
|
||||
import { StashService } from '../../core/stash.service';
|
||||
|
||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
import { MarkerStrings, SceneMarkerData, SceneData, AllTagsForFilter } from '../../core/graphql-generated';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-scene-detail-marker-manager',
|
||||
templateUrl: './scene-detail-marker-manager.component.html',
|
||||
styleUrls: ['./scene-detail-marker-manager.component.css']
|
||||
})
|
||||
export class SceneDetailMarkerManagerComponent implements OnInit, OnChanges {
|
||||
@Input() scene: SceneData.Fragment;
|
||||
@Input() player: any;
|
||||
|
||||
showingMarkerModal = false;
|
||||
markerOptions: MarkerStrings.Query['markerStrings'];
|
||||
filteredMarkerOptions: string[] = [];
|
||||
hasFocus = false;
|
||||
editingMarker: SceneMarkerData.Fragment;
|
||||
deleteClickCount = 0;
|
||||
|
||||
searchFormControl = new FormControl();
|
||||
|
||||
// Form input
|
||||
title: string;
|
||||
seconds: number;
|
||||
primary_tag_id: string;
|
||||
tag_ids: string[] = [];
|
||||
|
||||
// From the network
|
||||
tags: AllTagsForFilter.AllTags[];
|
||||
|
||||
constructor(private stashService: StashService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.stashService.allTagsForFilter().valueChanges.subscribe(result => {
|
||||
this.tags = result.data.allTags;
|
||||
});
|
||||
|
||||
this.stashService.markerStrings().valueChanges.subscribe(result => {
|
||||
this.markerOptions = result.data.markerStrings;
|
||||
});
|
||||
|
||||
this.searchFormControl.valueChanges.pipe(
|
||||
debounceTime(400),
|
||||
distinctUntilChanged()
|
||||
).subscribe(term => {
|
||||
this.filteredMarkerOptions = this.markerOptions.filter(value => {
|
||||
return value.title.toLowerCase().includes(term.toLowerCase());
|
||||
}).map(value => {
|
||||
return value.title;
|
||||
}).slice(0, 15);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['scene']) {
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
this.title = this.searchFormControl.value;
|
||||
const input = {
|
||||
id: null,
|
||||
title: this.title,
|
||||
seconds: this.seconds,
|
||||
scene_id: this.scene.id,
|
||||
primary_tag_id: this.primary_tag_id,
|
||||
tag_ids: this.tag_ids
|
||||
};
|
||||
|
||||
if (this.editingMarker == null) {
|
||||
this.stashService.markerCreate(input).subscribe(response => {
|
||||
console.log(response);
|
||||
this.hideModal();
|
||||
}, error => {
|
||||
console.log(error);
|
||||
});
|
||||
} else {
|
||||
input.id = this.editingMarker.id;
|
||||
this.stashService.markerUpdate(input).subscribe(response => {
|
||||
console.log(response);
|
||||
this.hideModal();
|
||||
}, error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.hideModal();
|
||||
}
|
||||
|
||||
onClickDelete() {
|
||||
this.deleteClickCount += 1;
|
||||
if (this.deleteClickCount > 2) {
|
||||
this.stashService.markerDestroy(this.editingMarker.id, this.scene.id).subscribe(response => {
|
||||
console.log('Delete successfull:', response);
|
||||
this.hideModal();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onClickAddMarker() {
|
||||
this.player.pause();
|
||||
this.showModal();
|
||||
}
|
||||
|
||||
onClickMarker(marker: SceneMarkerData.Fragment) {
|
||||
this.player.seek(marker.seconds);
|
||||
}
|
||||
|
||||
onClickEditMarker(marker: SceneMarkerData.Fragment) {
|
||||
this.showModal(marker);
|
||||
}
|
||||
|
||||
onClickMarkerTitle(title: string) {
|
||||
this.setTitle(title);
|
||||
}
|
||||
|
||||
setHasFocus(hasFocus: boolean) {
|
||||
if (hasFocus === false) {
|
||||
setTimeout(() => { this.hasFocus = false; }, 400);
|
||||
} else {
|
||||
this.hasFocus = hasFocus;
|
||||
}
|
||||
}
|
||||
|
||||
private hideModal() {
|
||||
this.showingMarkerModal = false;
|
||||
this.editingMarker = null;
|
||||
}
|
||||
|
||||
private showModal(marker: SceneMarkerData.Fragment = null) {
|
||||
this.deleteClickCount = 0;
|
||||
this.showingMarkerModal = true;
|
||||
|
||||
this.setTitle('');
|
||||
this.primary_tag_id = null;
|
||||
this.tag_ids = [];
|
||||
this.seconds = Math.round(this.player.getPosition());
|
||||
|
||||
if (marker == null) { return; }
|
||||
|
||||
this.editingMarker = marker;
|
||||
|
||||
this.setTitle(marker.title);
|
||||
this.seconds = marker.seconds;
|
||||
this.primary_tag_id = marker.primary_tag.id;
|
||||
this.tag_ids = marker.tags.map(value => value.id);
|
||||
}
|
||||
|
||||
private setTitle(title: string) {
|
||||
this.title = title;
|
||||
this.searchFormControl.setValue(title);
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
.scrubber-wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
#scrubber-back {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#scrubber-forward {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.scrubber-button {
|
||||
width: 1.5%;
|
||||
height: 100%;
|
||||
line-height: 120px;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
border: 1px solid #555;
|
||||
font-weight: 800;
|
||||
font-size: 20px;
|
||||
color: #FFF;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scrubber-content {
|
||||
-webkit-user-select: none;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
cursor: -webkit-grab;
|
||||
height: 120px;
|
||||
width: 96%;
|
||||
margin: 0 0.5%;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scrubber-content.dragging {
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
|
||||
.scrubber-tags-background {
|
||||
background-color: #555;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
#scrubber-position-indicator {
|
||||
background-color: #CCC;
|
||||
width: 100%;
|
||||
left: -100%;
|
||||
height: 20px;
|
||||
z-index: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#scrubber-current-position {
|
||||
background-color: #FFF;
|
||||
width: 2px;
|
||||
height: 30px;
|
||||
left: 50%;
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.scrubber-viewport {
|
||||
position: static;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scrubber-slider {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
transition: 333ms ease-out;
|
||||
}
|
||||
|
||||
.scrubber-tags {
|
||||
height: 20px;
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.scrubber-tag {
|
||||
position: absolute;
|
||||
background-color: #000;
|
||||
font-size: 10px;
|
||||
white-space: nowrap;
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.scrubber-tag:hover {
|
||||
z-index: 1;
|
||||
background-color: #444;
|
||||
}
|
||||
.scrubber-tag:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
border-top: solid 5px #000;
|
||||
border-left: solid 5px transparent;
|
||||
border-right: solid 5px transparent;
|
||||
}
|
||||
|
||||
.scrubber-item {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
text-shadow: 1px 1px black;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.scrubber-item span {
|
||||
display: inline-block;
|
||||
align-self: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<div class="scrubber-wrapper">
|
||||
<a class="scrubber-button" id="scrubber-back" (click)="goBack()"><</a>
|
||||
<div class="scrubber-content">
|
||||
<div class="scrubber-tags-background"></div>
|
||||
<div #positionIndicator id="scrubber-position-indicator"></div>
|
||||
<div id="scrubber-current-position"></div>
|
||||
<div class="scrubber-viewport">
|
||||
<div #scrubberSlider class="scrubber-slider">
|
||||
<div class="scrubber-tags">
|
||||
<div
|
||||
*ngFor="let marker of scene?.scene_markers; let i = index"
|
||||
#tag
|
||||
class="scrubber-tag"
|
||||
[attr.data-marker-id]="i"
|
||||
[ngStyle]="getTagStyle(tag, i)">
|
||||
{{marker.title}}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
*ngFor="let spriteItem of spriteItems; let i = index"
|
||||
class="scrubber-item"
|
||||
[attr.data-sprite-item-id]="i"
|
||||
[ngStyle]="getStyleForSprite(i)">
|
||||
<span>{{spriteItem.start | seconds}} - {{spriteItem.end | seconds}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="scrubber-button" id="scrubber-forward" (click)="goForward()">></a>
|
||||
</div>
|
||||
@@ -1,232 +0,0 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
Input,
|
||||
Output,
|
||||
HostListener,
|
||||
ViewChild,
|
||||
EventEmitter
|
||||
} from '@angular/core';
|
||||
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { SceneData } from '../../core/graphql-generated';
|
||||
|
||||
class SceneSpriteItem {
|
||||
start: number;
|
||||
end: number;
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-scene-detail-scrubber',
|
||||
templateUrl: './scene-detail-scrubber.component.html',
|
||||
styleUrls: ['./scene-detail-scrubber.component.css']
|
||||
})
|
||||
export class SceneDetailScrubberComponent implements OnInit, OnChanges {
|
||||
@Input() scene: SceneData.Fragment;
|
||||
@Output() seek: EventEmitter<number> = new EventEmitter();
|
||||
@Output() scrolled: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
slider: HTMLElement;
|
||||
@ViewChild('scrubberSlider') sliderTag: any;
|
||||
|
||||
indicator: HTMLElement;
|
||||
@ViewChild('positionIndicator') indicatorTag: any;
|
||||
|
||||
spriteItems: SceneSpriteItem[] = [];
|
||||
|
||||
private mouseDown = false;
|
||||
private last: MouseEvent;
|
||||
private start: MouseEvent;
|
||||
private velocity = 0;
|
||||
|
||||
private _position = 0;
|
||||
getPostion(): number { return this._position; }
|
||||
setPosition(newPostion: number, shouldEmit: boolean = true) {
|
||||
if (shouldEmit) { this.scrolled.emit(); }
|
||||
|
||||
const midpointOffset = this.slider.clientWidth / 2;
|
||||
|
||||
const bounds = this.getBounds() * -1;
|
||||
if (newPostion > midpointOffset) {
|
||||
this._position = midpointOffset;
|
||||
} else if (newPostion < bounds - midpointOffset) {
|
||||
this._position = bounds - midpointOffset;
|
||||
} else {
|
||||
this._position = newPostion;
|
||||
}
|
||||
|
||||
this.slider.style.transform = `translateX(${this._position}px)`;
|
||||
|
||||
const indicatorPosition = ((newPostion - midpointOffset) / (bounds - (midpointOffset * 2)) * this.slider.clientWidth);
|
||||
this.indicator.style.transform = `translateX(${indicatorPosition}px)`;
|
||||
}
|
||||
|
||||
@HostListener('window:mouseup', ['$event'])
|
||||
onMouseup(event: MouseEvent) {
|
||||
if (!this.start) { return; }
|
||||
this.mouseDown = false;
|
||||
const delta = Math.abs(event.clientX - this.start.clientX);
|
||||
if (delta < 1 && event.target instanceof HTMLDivElement) {
|
||||
const target: HTMLDivElement = event.target;
|
||||
let seekSeconds: number = null;
|
||||
|
||||
const spriteIdString = target.getAttribute('data-sprite-item-id');
|
||||
if (spriteIdString != null) {
|
||||
const spritePercentage = event.offsetX / target.clientWidth;
|
||||
const offset = target.offsetLeft + (target.clientWidth * spritePercentage);
|
||||
const percentage = offset / this.slider.scrollWidth;
|
||||
seekSeconds = percentage * this.scene.file.duration;
|
||||
}
|
||||
|
||||
const markerIdString = target.getAttribute('data-marker-id');
|
||||
if (markerIdString != null) {
|
||||
const marker = this.scene.scene_markers[Number(markerIdString)];
|
||||
seekSeconds = marker.seconds;
|
||||
}
|
||||
|
||||
if (!!seekSeconds) { this.seek.emit(seekSeconds); }
|
||||
} else if (Math.abs(this.velocity) > 25) {
|
||||
const newPosition = this.getPostion() + (this.velocity * 10);
|
||||
this.setPosition(newPosition);
|
||||
this.velocity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('mousedown', ['$event'])
|
||||
onMousedown(event) {
|
||||
event.preventDefault();
|
||||
this.mouseDown = true;
|
||||
this.last = event;
|
||||
this.start = event;
|
||||
this.velocity = 0;
|
||||
}
|
||||
|
||||
@HostListener('mousemove', ['$event'])
|
||||
onMousemove(event: MouseEvent) {
|
||||
if (!this.mouseDown) { return; }
|
||||
|
||||
// negative dragging right (past), positive left (future)
|
||||
const delta = event.clientX - this.last.clientX;
|
||||
|
||||
const movement = event.movementX;
|
||||
this.velocity = movement;
|
||||
|
||||
const newPostion = this.getPostion() + delta;
|
||||
this.setPosition(newPostion);
|
||||
this.last = event;
|
||||
}
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.slider = this.sliderTag.nativeElement;
|
||||
this.indicator = this.indicatorTag.nativeElement;
|
||||
|
||||
this.slider.style.transform = `translateX(${this.slider.clientWidth / 2}px)`;
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['scene']) {
|
||||
this.fetchSpriteInfo();
|
||||
}
|
||||
}
|
||||
|
||||
fetchSpriteInfo() {
|
||||
if (!this.scene) { return; }
|
||||
|
||||
this.http.get(this.scene.paths.vtt, {responseType: 'text'}).subscribe(res => {
|
||||
// TODO: This is gnarly
|
||||
const lines = res.split('\n');
|
||||
if (lines.shift() !== 'WEBVTT') { return; }
|
||||
if (lines.shift() !== '') { return; }
|
||||
let item = new SceneSpriteItem();
|
||||
this.spriteItems = [];
|
||||
while (lines.length) {
|
||||
const line = lines.shift();
|
||||
|
||||
if (line.includes('#') && line.includes('=') && line.includes(',')) {
|
||||
const size = line.split('#')[1].split('=')[1].split(',');
|
||||
item.x = Number(size[0]);
|
||||
item.y = Number(size[1]);
|
||||
item.w = Number(size[2]);
|
||||
item.h = Number(size[3]);
|
||||
|
||||
this.spriteItems.push(item);
|
||||
item = new SceneSpriteItem();
|
||||
} else if (line.includes(' --> ')) {
|
||||
const times = line.split(' --> ');
|
||||
|
||||
const start = times[0].split(':');
|
||||
item.start = (+start[0]) * 60 * 60 + (+start[1]) * 60 + (+start[2]);
|
||||
|
||||
const end = times[1].split(':');
|
||||
item.end = (+end[0]) * 60 * 60 + (+end[1]) * 60 + (+end[2]);
|
||||
}
|
||||
}
|
||||
}, error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
getBounds(): number {
|
||||
return this.slider.scrollWidth - this.slider.clientWidth;
|
||||
}
|
||||
|
||||
getStyleForSprite(i) {
|
||||
const sprite = this.spriteItems[i];
|
||||
const left = sprite.w * i;
|
||||
const path = this.scene.paths.vtt.replace('_thumbs.vtt', '_sprite.jpg'); // TODO: Gnarly
|
||||
return {
|
||||
'width.px': sprite.w,
|
||||
'height.px': sprite.h,
|
||||
'margin': '0px auto',
|
||||
'background-position': -sprite.x + 'px ' + -sprite.y + 'px',
|
||||
'background-image': `url(${path})`,
|
||||
'left.px': left
|
||||
};
|
||||
}
|
||||
|
||||
getTagStyle(tag: HTMLDivElement, i: number) {
|
||||
if (!this.slider || this.spriteItems.length === 0 || this.getBounds() === 0) { return {}; }
|
||||
|
||||
const marker = this.scene.scene_markers[i];
|
||||
const duration = Number(this.scene.file.duration);
|
||||
const percentage = marker.seconds / duration;
|
||||
|
||||
// TODO: this doesn't seem necessary anymore. Double check.
|
||||
// Need to offset from the left margin or the tags are slightly off.
|
||||
// const offset = Number(window.getComputedStyle(this.slider.offsetParent).marginLeft.replace('px', ''));
|
||||
const offset = 0;
|
||||
|
||||
const left = (this.slider.scrollWidth * percentage) - (tag.clientWidth / 2) + offset;
|
||||
return {
|
||||
'left.px': left,
|
||||
'height.px': 20
|
||||
};
|
||||
}
|
||||
|
||||
goBack() {
|
||||
const newPosition = this.getPostion() + this.slider.clientWidth;
|
||||
this.setPosition(newPosition);
|
||||
}
|
||||
|
||||
goForward() {
|
||||
const newPosition = this.getPostion() - this.slider.clientWidth;
|
||||
this.setPosition(newPosition);
|
||||
}
|
||||
|
||||
public scrollTo(seconds: number) {
|
||||
const duration = Number(this.scene.file.duration);
|
||||
const percentage = seconds / duration;
|
||||
const position = ((this.slider.scrollWidth * percentage) - (this.slider.clientWidth / 2)) * -1;
|
||||
this.setPosition(position, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
<app-jwplayer
|
||||
(seeked)="onSeeked()"
|
||||
(time)="onTime($event)"
|
||||
#jwplayer>
|
||||
</app-jwplayer>
|
||||
|
||||
<div class="ui container segments">
|
||||
<div class="ui inverted top attached segment">
|
||||
|
||||
<app-scene-detail-scrubber
|
||||
#scrubber
|
||||
[scene]="scene"
|
||||
(seek)="scrubberSeek($event)"
|
||||
(scrolled)="scrubberScrolled()">
|
||||
</app-scene-detail-scrubber>
|
||||
|
||||
<app-scene-detail-marker-manager
|
||||
#markerManager
|
||||
[scene]="scene"
|
||||
[player]="jwplayer.player">
|
||||
</app-scene-detail-marker-manager>
|
||||
</div>
|
||||
|
||||
<div class="ui inverted attached clearing segment">
|
||||
<h1 class="ui inverted left floated marginless header">
|
||||
{{scene?.title || 'No Title'}}
|
||||
<div *ngIf="!!scene?.date" class="sub header">{{scene?.date | date:"MM/dd/yy"}}</div>
|
||||
<div class="sub header">{{scene?.file.size | fileSize}}</div>
|
||||
</h1>
|
||||
<button (click)="onClickEdit()" class="ui right floated button">Edit</button>
|
||||
|
||||
<div *ngIf="!!scene">
|
||||
<a *ngIf="!!scene.studio"
|
||||
[routerLink]="['/studios', scene.studio.id]"
|
||||
[style.background-image]="'url(' + scene.studio.image_path + ')'"
|
||||
style="width: 100%; height: 100px; display: inline-block; background-size: contain; background-position: center; background-repeat: no-repeat; filter: drop-shadow( 5px 5px 4px #aaa );">
|
||||
</a>
|
||||
<span *ngIf="!scene.studio">No Studio</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!!scene?.details && scene?.details.length != 0" class="ui inverted attached segment">
|
||||
<h3>Details</h3>
|
||||
<p class="pre">{{scene.details}}</p>
|
||||
</div>
|
||||
|
||||
<div *ngIf="scene?.performers.length > 0" class="ui inverted attached segment">
|
||||
<h3>Performers</h3>
|
||||
<div class="ui four centered stackable link cards">
|
||||
<app-performer-card *ngFor="let performer of scene?.performers"
|
||||
[performer]="performer"
|
||||
[ageFromDate]="scene.date">
|
||||
</app-performer-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="scene?.tags.length > 0" class="ui inverted attached segment">
|
||||
<h3>Tags</h3>
|
||||
<div class="ui labels">
|
||||
<a *ngFor="let tag of scene?.tags" class="ui label">{{tag.name}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!!scene?.gallery" class="ui inverted attached segment">
|
||||
<h3 class="ui header">
|
||||
Gallery
|
||||
</h3>
|
||||
<app-gallery-preview *ngIf="!!scene?.gallery" [gallery]="scene?.gallery"></app-gallery-preview>
|
||||
</div>
|
||||
|
||||
<div class="ui inverted bottom attached segment">
|
||||
<div class="ui inverted list">
|
||||
<a class="clippable item" title="Click to copy" ngxClipboard [cbContent]="scene?.checksum">
|
||||
<i class="privacy icon"></i>
|
||||
<div class="content">
|
||||
<div class="header">Checksum</div>
|
||||
<div class="description">{{scene?.checksum}}</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="clippable item" title="Click to copy" ngxClipboard [cbContent]="scene?.path">
|
||||
<i class="folder open outline icon"></i>
|
||||
<div class="content">
|
||||
<div class="header">Path</div>
|
||||
<div class="description">{{scene?.path}}</div>
|
||||
</div>
|
||||
</a>
|
||||
<a *ngIf="!!scene?.url" class="clippable item" title="Click to copy" ngxClipboard [cbContent]="scene?.url">
|
||||
<i class="server icon"></i>
|
||||
<div class="content">
|
||||
<div class="header">URL</div>
|
||||
<div class="description">{{scene?.url}}</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,82 +0,0 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { StashService } from '../../core/stash.service';
|
||||
|
||||
import { SceneData } from '../../core/graphql-generated';
|
||||
|
||||
@Component({
|
||||
selector: 'app-scene-detail',
|
||||
templateUrl: './scene-detail.component.html',
|
||||
styleUrls: ['./scene-detail.component.css']
|
||||
})
|
||||
export class SceneDetailComponent implements OnInit {
|
||||
scene: SceneData.Fragment;
|
||||
|
||||
private lastTime = 0;
|
||||
|
||||
private isPlayerSetup = false;
|
||||
|
||||
@ViewChild('jwplayer') jwplayer: any;
|
||||
@ViewChild('scrubber') scrubber: any;
|
||||
|
||||
constructor(private route: ActivatedRoute, private stashService: StashService, private router: Router) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.getScene();
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
getScene() {
|
||||
const id = parseInt(this.route.snapshot.params['id'], 10);
|
||||
|
||||
this.stashService.findScene(id).valueChanges.subscribe(result => {
|
||||
this.scene = Object.assign({scene_marker_tags: result.data.sceneMarkerTags}, result.data.findScene);
|
||||
|
||||
// TODO: Check this, this didn't matter before...
|
||||
if (!this.isPlayerSetup) {
|
||||
const streamPath = this.scene.paths.stream;
|
||||
const screenshotPath = this.scene.paths.screenshot;
|
||||
const vttPath = this.scene.paths.vtt;
|
||||
const chaptersVttPath = this.scene.paths.chapters_vtt;
|
||||
this.jwplayer.setupPlayer(streamPath, screenshotPath, vttPath, chaptersVttPath);
|
||||
this.isPlayerSetup = true;
|
||||
|
||||
this.route.queryParams.subscribe(params => {
|
||||
if (params['t'] != null) {
|
||||
this.jwplayer.player.seek(params['t']);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
onClickEdit() {
|
||||
this.router.navigate(['edit'], { relativeTo: this.route });
|
||||
}
|
||||
|
||||
onSeeked() {
|
||||
const position = this.jwplayer.player.getPosition();
|
||||
this.scrubber.scrollTo(position);
|
||||
this.jwplayer.player.play();
|
||||
}
|
||||
|
||||
onTime(data) {
|
||||
const position = this.jwplayer.player.getPosition();
|
||||
const difference = Math.abs(position - this.lastTime);
|
||||
if (difference > 1) {
|
||||
this.lastTime = position;
|
||||
this.scrubber.scrollTo(position);
|
||||
}
|
||||
}
|
||||
|
||||
scrubberSeek(seconds) {
|
||||
this.jwplayer.player.seek(seconds);
|
||||
}
|
||||
|
||||
scrubberScrolled() {
|
||||
this.jwplayer.player.pause();
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
<div [class.loading]="loading" class="ui inverted form">
|
||||
<h4 class="ui inverted dividing header">Scene Information</h4>
|
||||
<div class="field">
|
||||
<div class="equal width fields">
|
||||
<div class="field">
|
||||
<label>Title</label>
|
||||
<input [(ngModel)]="title" type="text" placeholder="Title" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>URL</label>
|
||||
<input [(ngModel)]="url" type="url" placeholder="URL" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Date (YYYY-MM-DD)</label>
|
||||
<input [(ngModel)]="date" type="text" placeholder="Date (YYYY-MM-DD)" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Rating</label>
|
||||
<sui-rating class="ui massive rating" [(ngModel)]="rating" [maximum]="5"></sui-rating>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Gallery</label>
|
||||
<sui-select
|
||||
class="selection"
|
||||
[(ngModel)]="gallery_id"
|
||||
[options]="galleries"
|
||||
labelField="path"
|
||||
valueField="id"
|
||||
[isSearchable]="true"
|
||||
placeholder="Gallery"
|
||||
#gallerySelect>
|
||||
<sui-select-option [value]="{id: 0, path: 'None'}"></sui-select-option>
|
||||
<sui-select-option *ngFor="let option of gallerySelect.availableOptions" [value]="option"></sui-select-option>
|
||||
</sui-select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Studio</label>
|
||||
<sui-select
|
||||
class="selection"
|
||||
[(ngModel)]="studio_id"
|
||||
[options]="studios"
|
||||
labelField="name"
|
||||
valueField="id"
|
||||
[isSearchable]="true"
|
||||
placeholder="Studio"
|
||||
#studioSelect>
|
||||
<sui-select-option *ngFor="let option of studioSelect.availableOptions" [value]="option"></sui-select-option>
|
||||
</sui-select>
|
||||
</div>
|
||||
|
||||
<ng-template let-option #performerOptionTemplate>
|
||||
<img [lazyLoad]="option.image_path" height="80"/> {{ option.name }}
|
||||
</ng-template>
|
||||
|
||||
<div class="field">
|
||||
<label>Performers</label>
|
||||
<sui-multi-select
|
||||
class="selection"
|
||||
[(ngModel)]="performer_ids"
|
||||
[options]="performers"
|
||||
labelField="name"
|
||||
valueField="id"
|
||||
[isSearchable]="true"
|
||||
[optionTemplate]="performerOptionTemplate"
|
||||
placeholder="Performers"
|
||||
#performerSelect>
|
||||
<sui-select-option *ngFor="let option of performerSelect.availableOptions" [value]="option"></sui-select-option>
|
||||
</sui-multi-select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Tags</label>
|
||||
<div class="fields">
|
||||
<div class="fourteen wide field">
|
||||
<sui-multi-select
|
||||
class="selection"
|
||||
[(ngModel)]="tag_ids"
|
||||
[options]="tags"
|
||||
labelField="name"
|
||||
valueField="id"
|
||||
[isSearchable]="true"
|
||||
placeholder="Tags"
|
||||
#tagSelect>
|
||||
<sui-select-option *ngFor="let option of tagSelect.availableOptions" [value]="option"></sui-select-option>
|
||||
</sui-multi-select>
|
||||
</div>
|
||||
<div class="two wide field">
|
||||
<%= link_to 'Add Tag', new_tag_path, class: 'ui fluid button' %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Details</label>
|
||||
<textarea [(ngModel)]="details" placeholder="Details"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<button (click)="onSubmit()" class="ui primary submit button">Submit</button>
|
||||
</div>
|
||||
@@ -1,87 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { StashService } from '../../core/stash.service';
|
||||
|
||||
import { FindSceneForEditing } from '../../core/graphql-generated';
|
||||
|
||||
@Component({
|
||||
selector: 'app-scene-form',
|
||||
templateUrl: './scene-form.component.html',
|
||||
styleUrls: ['./scene-form.component.css']
|
||||
})
|
||||
export class SceneFormComponent implements OnInit {
|
||||
loading = true;
|
||||
|
||||
title: string;
|
||||
details: string;
|
||||
url: string;
|
||||
date: string;
|
||||
rating: number;
|
||||
gallery_id: string;
|
||||
studio_id: string;
|
||||
performer_ids: string[] = [];
|
||||
tag_ids: string[] = [];
|
||||
|
||||
performers: FindSceneForEditing.Query['allPerformers'];
|
||||
tags: FindSceneForEditing.Query['allTags'];
|
||||
studios: FindSceneForEditing.Query['allStudios'];
|
||||
galleries: FindSceneForEditing.Query['validGalleriesForScene'];
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private stashService: StashService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.getScene();
|
||||
}
|
||||
|
||||
getScene() {
|
||||
const id = parseInt(this.route.snapshot.params['id'], 10);
|
||||
|
||||
if (!!id === false) {
|
||||
console.log('new scene');
|
||||
return;
|
||||
}
|
||||
|
||||
this.stashService.findSceneForEditing(id).valueChanges.subscribe(result => {
|
||||
this.title = result.data.findScene.title;
|
||||
this.details = result.data.findScene.details;
|
||||
this.url = result.data.findScene.url;
|
||||
this.date = result.data.findScene.date;
|
||||
this.rating = result.data.findScene.rating;
|
||||
this.gallery_id = !!result.data.findScene.gallery ? result.data.findScene.gallery.id : null;
|
||||
this.studio_id = !!result.data.findScene.studio ? result.data.findScene.studio.id : null;
|
||||
this.performer_ids = result.data.findScene.performers.map(performer => performer.id);
|
||||
this.tag_ids = result.data.findScene.tags.map(tag => tag.id);
|
||||
|
||||
this.performers = result.data.allPerformers;
|
||||
this.tags = result.data.allTags;
|
||||
this.studios = result.data.allStudios;
|
||||
this.galleries = result.data.validGalleriesForScene;
|
||||
|
||||
this.loading = result.loading;
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
const id = this.route.snapshot.params['id'];
|
||||
this.stashService.sceneUpdate({
|
||||
id: id,
|
||||
title: this.title,
|
||||
details: this.details,
|
||||
url: this.url,
|
||||
date: this.date,
|
||||
rating: this.rating,
|
||||
studio_id: this.studio_id,
|
||||
gallery_id: this.gallery_id,
|
||||
performer_ids: this.performer_ids,
|
||||
tag_ids: this.tag_ids
|
||||
}).subscribe(result => {
|
||||
this.router.navigate(['/scenes', id]);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { ScenesService } from '../scenes.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-scene-list',
|
||||
template: '<app-list [state]="state"></app-list>'
|
||||
})
|
||||
export class SceneListComponent implements OnInit {
|
||||
state = this.scenesService.sceneListState;
|
||||
|
||||
constructor(private scenesService: ScenesService) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<div class="simple-modal-container" [style.display]="showingMarkerList ? 'block' : 'none'">
|
||||
<div class="simple-modal-content">
|
||||
<table class="ui very basic celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th (click)="sortMarkers('title')">Title</th>
|
||||
<th (click)="sortMarkers('count')">Scene Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let marker of markerOptions">
|
||||
<td><a (click)="onClickMarker(marker)">{{marker.title}}</a></td>
|
||||
<td>{{marker.count}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wall grid">
|
||||
<form id="scene-filter" class="ui inverted smassive form">
|
||||
<div class="ui inverted massive fluid transparent icon input">
|
||||
<input [formControl]="searchFormControl" name="q" placeholder="Search..." type="text">
|
||||
<i class="search icon"></i>
|
||||
<button (click)="refresh()" class="ui button" style="margin: 5px 5px;">Refresh</button>
|
||||
<button *ngIf="mode == WallMode.Markers" (click)="toggleMarkerList()" class="ui button" style="margin: 5px 5px;">List Markers</button>
|
||||
<button (click)="toggleMode()" class="ui button" style="margin: 5px 50px 5px 5px;">{{mode == WallMode.Scenes ? 'Scenes' : 'Markers'}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="ui five column grid" style="margin: 0;">
|
||||
<div *ngFor="let item of items" class="wall column">
|
||||
<app-scene-wall-item *ngIf="mode == WallMode.Markers" [marker]="item"></app-scene-wall-item>
|
||||
<app-scene-wall-item *ngIf="mode == WallMode.Scenes" [scene]="item"></app-scene-wall-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,84 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
|
||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
import { StashService } from '../../core/stash.service';
|
||||
|
||||
export enum WallMode {
|
||||
Scenes,
|
||||
Markers
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-scene-wall',
|
||||
templateUrl: './scene-wall.component.html',
|
||||
styleUrls: ['./scene-wall.component.css']
|
||||
})
|
||||
export class SceneWallComponent implements OnInit {
|
||||
WallMode = WallMode;
|
||||
items: any[]; // scenes or scene markers
|
||||
markerOptions: any[];
|
||||
showingMarkerList = false;
|
||||
searchTerm = '';
|
||||
searchFormControl = new FormControl();
|
||||
mode: WallMode = WallMode.Markers;
|
||||
|
||||
constructor(
|
||||
private stashService: StashService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.searchFormControl.valueChanges.pipe(
|
||||
debounceTime(1000),
|
||||
distinctUntilChanged()
|
||||
).subscribe(term => {
|
||||
this.getScenes(term);
|
||||
});
|
||||
this.stashService.markerStrings().valueChanges.subscribe(result => {
|
||||
this.markerOptions = result.data.markerStrings;
|
||||
});
|
||||
this.searchFormControl.setValue(this.searchTerm);
|
||||
}
|
||||
|
||||
async getScenes(q: string) {
|
||||
this.items = null;
|
||||
this.searchTerm = q;
|
||||
if (this.mode === WallMode.Scenes) {
|
||||
const response = await this.stashService.sceneWall(q).result();
|
||||
this.items = response.data.sceneWall;
|
||||
} else {
|
||||
const response = await this.stashService.markerWall(q).result();
|
||||
this.items = response.data.markerWall;
|
||||
}
|
||||
}
|
||||
|
||||
toggleMode() {
|
||||
if (this.mode === WallMode.Scenes) {
|
||||
this.mode = WallMode.Markers;
|
||||
} else {
|
||||
this.mode = WallMode.Scenes;
|
||||
}
|
||||
this.getScenes(this.searchTerm);
|
||||
}
|
||||
|
||||
toggleMarkerList() {
|
||||
this.showingMarkerList = !this.showingMarkerList;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.getScenes(this.searchTerm);
|
||||
}
|
||||
|
||||
onClickMarker(marker) {
|
||||
this.searchTerm = `${marker.title}`;
|
||||
this.searchFormControl.setValue(this.searchTerm);
|
||||
this.showingMarkerList = false;
|
||||
}
|
||||
|
||||
async sortMarkers(by) {
|
||||
const result = await this.stashService.markerStrings(null, by).result();
|
||||
this.markerOptions = result.data.markerStrings;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { ScenesComponent } from './scenes/scenes.component';
|
||||
import { SceneListComponent } from './scene-list/scene-list.component';
|
||||
import { SceneDetailComponent } from './scene-detail/scene-detail.component';
|
||||
import { SceneFormComponent } from './scene-form/scene-form.component';
|
||||
import { SceneWallComponent } from './scene-wall/scene-wall.component';
|
||||
import { MarkerListComponent } from './marker-list/marker-list.component';
|
||||
|
||||
const scenesRoutes: Routes = [
|
||||
{ path: 'wall', component: SceneWallComponent },
|
||||
{ path: 'markers', component: MarkerListComponent },
|
||||
{ path: '',
|
||||
component: ScenesComponent,
|
||||
children: [
|
||||
{ path: '', component: SceneListComponent },
|
||||
{ path: ':id', component: SceneDetailComponent },
|
||||
{ path: ':id/edit', component: SceneFormComponent }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(scenesRoutes)
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class ScenesRoutingModule {}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
import { ScenesRoutingModule } from './scenes-routing.module';
|
||||
import { ScenesService } from './scenes.service';
|
||||
|
||||
import { ScenesComponent } from './scenes/scenes.component';
|
||||
import { SceneListComponent } from './scene-list/scene-list.component';
|
||||
import { SceneDetailComponent } from './scene-detail/scene-detail.component';
|
||||
import { SceneFormComponent } from './scene-form/scene-form.component';
|
||||
import { SceneWallComponent } from './scene-wall/scene-wall.component';
|
||||
import { SceneDetailScrubberComponent } from './scene-detail-scrubber/scene-detail-scrubber.component';
|
||||
import { SceneDetailMarkerManagerComponent } from './scene-detail-marker-manager/scene-detail-marker-manager.component';
|
||||
import { MarkerListComponent } from './marker-list/marker-list.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
ScenesRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
ScenesComponent,
|
||||
SceneListComponent,
|
||||
SceneDetailComponent,
|
||||
SceneFormComponent,
|
||||
SceneWallComponent,
|
||||
SceneDetailScrubberComponent,
|
||||
SceneDetailMarkerManagerComponent,
|
||||
MarkerListComponent
|
||||
],
|
||||
providers: [
|
||||
ScenesService
|
||||
]
|
||||
})
|
||||
export class ScenesModule {}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { SceneListState, SceneMarkerListState } from '../shared/models/list-state.model';
|
||||
|
||||
@Injectable()
|
||||
export class ScenesService {
|
||||
sceneListState: SceneListState = new SceneListState();
|
||||
sceneMarkerListState: SceneMarkerListState = new SceneMarkerListState();
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-scenes',
|
||||
template: '<router-outlet></router-outlet>'
|
||||
})
|
||||
export class ScenesComponent implements OnInit {
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { SettingsComponent } from './settings/settings.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '',
|
||||
component: SettingsComponent
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class SettingsRoutingModule {}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
import { SettingsRoutingModule } from './settings-routing.module';
|
||||
import { SettingsComponent } from './settings/settings.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
SettingsRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
SettingsComponent
|
||||
]
|
||||
})
|
||||
export class SettingsModule {}
|
||||
@@ -1,20 +0,0 @@
|
||||
<div class="ui text menu">
|
||||
<div class="left menu">
|
||||
<button (click)="onClickImport()" class="ui button">Import (Click {{3 - importClickCount}} times)</button>
|
||||
<button (click)="onClickExport()" class="ui button">Export</button>
|
||||
</div>
|
||||
<div class="right menu">
|
||||
<button (click)="onClickScan()" class="ui button">Scan</button>
|
||||
<button (click)="onClickGenerate()" class="ui button">Generate</button>
|
||||
<button (click)="onClickClean()" class="ui button">Clean</button>
|
||||
</div>
|
||||
</div>
|
||||
<sui-progress class="indicating" [value]="progress">{{message}}</sui-progress>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
|
||||
<div class="ui list" style="color: white;">
|
||||
<div *ngFor="let log of logs" class="item">
|
||||
<strong>{{log.type}}</strong> - {{log.message}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,58 +0,0 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { StashService } from '../../core/stash.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings',
|
||||
templateUrl: './settings.component.html'
|
||||
})
|
||||
export class SettingsComponent implements OnInit, OnDestroy {
|
||||
progress: number;
|
||||
message: string;
|
||||
logs: string[];
|
||||
statusObservable: Subscription;
|
||||
importClickCount = 0;
|
||||
|
||||
constructor(private stashService: StashService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.statusObservable = this.stashService.metadataUpdate().subscribe(response => {
|
||||
const result = JSON.parse(response.data.metadataUpdate);
|
||||
|
||||
this.progress = result.progress;
|
||||
this.message = result.message;
|
||||
this.logs = result.logs;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (!this.statusObservable) { return; }
|
||||
this.statusObservable.unsubscribe();
|
||||
}
|
||||
|
||||
onClickImport() {
|
||||
this.importClickCount += 1;
|
||||
if (this.importClickCount > 2) {
|
||||
this.stashService.metadataImport().refetch();
|
||||
this.importClickCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
onClickExport() {
|
||||
this.stashService.metadataExport().refetch();
|
||||
}
|
||||
|
||||
onClickScan() {
|
||||
this.stashService.metadataScan().refetch();
|
||||
}
|
||||
|
||||
onClickGenerate() {
|
||||
this.stashService.metadataGenerate().refetch();
|
||||
}
|
||||
|
||||
onClickClean() {
|
||||
this.stashService.metadataClean().refetch();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'age'
|
||||
})
|
||||
export class AgePipe implements PipeTransform {
|
||||
|
||||
transform(value: string, ageFromDate?: string): number {
|
||||
if (!!value === false) { return 0; }
|
||||
|
||||
const birthdate = new Date(value);
|
||||
const fromDate = !!ageFromDate ? new Date(ageFromDate) : new Date();
|
||||
|
||||
let age = fromDate.getFullYear() - birthdate.getFullYear();
|
||||
if (birthdate.getMonth() > fromDate.getMonth() ||
|
||||
(birthdate.getMonth() >= fromDate.getMonth() && birthdate.getDay() > fromDate.getDay())) {
|
||||
age -= 1;
|
||||
}
|
||||
|
||||
return age;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import { Component, OnInit, ViewChild, ElementRef, HostListener } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-base-wall-item',
|
||||
template: ''
|
||||
})
|
||||
export class BaseWallItemComponent implements OnInit {
|
||||
private video: any;
|
||||
private hoverTimeout: any = null;
|
||||
isHovering = false;
|
||||
|
||||
title = '';
|
||||
imagePath = '';
|
||||
videoPath = '';
|
||||
|
||||
@ViewChild('videoTag')
|
||||
set videoTag(videoTag: ElementRef) {
|
||||
if (videoTag === undefined) { return; }
|
||||
this.video = videoTag.nativeElement;
|
||||
this.video.volume = 0.05;
|
||||
this.video.loop = true;
|
||||
this.video.oncanplay = () => {
|
||||
this.video.play();
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
@HostListener('mouseenter', ['$event'])
|
||||
onMouseEnter(e) {
|
||||
if (!!this.hoverTimeout) { return; }
|
||||
|
||||
const that = this;
|
||||
this.hoverTimeout = setTimeout(function() {
|
||||
that.configureTimeout(e);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
@HostListener('mouseleave')
|
||||
onMouseLeave() {
|
||||
if (!!this.hoverTimeout) {
|
||||
clearTimeout(this.hoverTimeout);
|
||||
this.hoverTimeout = null;
|
||||
}
|
||||
if (this.video !== undefined) {
|
||||
this.video.pause();
|
||||
this.video.src = '';
|
||||
}
|
||||
this.isHovering = false;
|
||||
}
|
||||
|
||||
@HostListener('mousemove', ['$event'])
|
||||
onMouseMove(event: MouseEvent) {
|
||||
if (!!this.hoverTimeout) {
|
||||
clearTimeout(this.hoverTimeout);
|
||||
this.hoverTimeout = null;
|
||||
}
|
||||
this.configureTimeout(event);
|
||||
}
|
||||
|
||||
transitionEnd(event) {
|
||||
if (event.target.classList.contains('double-scale')) {
|
||||
event.target.style.zIndex = 2;
|
||||
} else {
|
||||
event.target.style.zIndex = null;
|
||||
}
|
||||
}
|
||||
|
||||
private configureTimeout(event: MouseEvent) {
|
||||
const that = this;
|
||||
this.hoverTimeout = setTimeout(function() {
|
||||
if (event.target instanceof HTMLElement) {
|
||||
const target: HTMLElement = event.target;
|
||||
if (target.className === 'scene-wall-item-text-container' ||
|
||||
target.offsetParent.className === 'scene-wall-item-text-container') {
|
||||
that.configureTimeout(event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
that.isHovering = true;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'capitalize'
|
||||
})
|
||||
export class CapitalizePipe implements PipeTransform {
|
||||
|
||||
transform(value: any, args?: any): any {
|
||||
if (value) {
|
||||
return value.charAt(0).toUpperCase() + value.slice(1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'filename'
|
||||
})
|
||||
export class FileNamePipe implements PipeTransform {
|
||||
|
||||
transform(value: string, args?: any): string {
|
||||
if (!!value === false) { return 'No File Name'; }
|
||||
return value.replace(/^.*[\\\/]/, '');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'fileSize'
|
||||
})
|
||||
export class FileSizePipe implements PipeTransform {
|
||||
|
||||
private units = [
|
||||
'bytes',
|
||||
'kB',
|
||||
'MB',
|
||||
'GB',
|
||||
'TB',
|
||||
'PB'
|
||||
];
|
||||
|
||||
transform(bytes: number = 0, precision: number = 2): string {
|
||||
if (isNaN(parseFloat(String(bytes))) || !isFinite(bytes)) { return '?'; }
|
||||
|
||||
let unit = 0;
|
||||
while ( bytes >= 1024 ) {
|
||||
bytes /= 1024;
|
||||
unit++;
|
||||
}
|
||||
|
||||
return bytes.toFixed(+precision) + ' ' + this.units[ unit ];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<app-gallery-preview
|
||||
[gallery]="gallery"
|
||||
[numberOfRandomImages]="4"
|
||||
[showTitles]="false"
|
||||
[numberPerRow]="2">
|
||||
</app-gallery-preview>
|
||||
<div class="content">
|
||||
<div class="header">{{gallery.title || gallery.path}}</div>
|
||||
</div>
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Component, OnInit, Input, HostBinding } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { GalleryData } from '../../core/graphql-generated';
|
||||
|
||||
@Component({
|
||||
selector: 'app-gallery-card',
|
||||
templateUrl: './gallery-card.component.html',
|
||||
styleUrls: ['./gallery-card.component.css']
|
||||
})
|
||||
export class GalleryCardComponent implements OnInit {
|
||||
@Input() gallery: GalleryData.Fragment;
|
||||
|
||||
// The host class needs to be card
|
||||
@HostBinding('class') class = 'card';
|
||||
|
||||
constructor(
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
onSelect(): void {
|
||||
this.router.navigate(['/galleries', this.gallery.id]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<div (click)="onClickGallery()" class="ui grid" style="margin: 0;">
|
||||
<div
|
||||
*ngFor="let image of files"
|
||||
[lazyLoad]="imagePath(image)"
|
||||
(click)="onClickImage(image)"
|
||||
[style.min-height]="(numberOfRandomImages <= 4) ? '200px' : '400px'"
|
||||
style="height: 15vh; background-size: cover; background-position: 50% 25%;"
|
||||
class="{{suiWidthForNumberPerRow()}} wide column">
|
||||
<h3 *ngIf="showTitles">{{image.name}}</h3>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,98 +0,0 @@
|
||||
import { Component, OnInit, OnChanges, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { StashService } from '../../core/stash.service';
|
||||
|
||||
import { GalleryImage } from '../../shared/models/gallery.model';
|
||||
import { GalleryData } from '../../core/graphql-generated';
|
||||
|
||||
@Component({
|
||||
selector: 'app-gallery-preview',
|
||||
templateUrl: './gallery-preview.component.html',
|
||||
styleUrls: ['./gallery-preview.component.css']
|
||||
})
|
||||
export class GalleryPreviewComponent implements OnInit, OnChanges {
|
||||
@Input() gallery: GalleryData.Fragment;
|
||||
@Input() galleryId: number;
|
||||
@Input() type = 'random';
|
||||
@Input() numberOfRandomImages = 12;
|
||||
@Input() showTitles = true;
|
||||
@Input() numberPerRow = 4;
|
||||
@Output() clicked: EventEmitter<GalleryImage> = new EventEmitter();
|
||||
files: GalleryImage[];
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private stashService: StashService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (!!this.galleryId) {
|
||||
this.fetchGallery();
|
||||
}
|
||||
}
|
||||
|
||||
async fetchGallery() {
|
||||
const result = await this.stashService.findGallery(this.galleryId).result();
|
||||
this.gallery = result.data.findGallery;
|
||||
this.setupFiles();
|
||||
}
|
||||
|
||||
imagePath(image) {
|
||||
return `${image.path}?thumb=true`;
|
||||
}
|
||||
|
||||
shuffle(a) {
|
||||
for (let i = a.length; i; i--) {
|
||||
const j = Math.floor(Math.random() * i);
|
||||
[a[i - 1], a[j]] = [a[j], a[i - 1]];
|
||||
}
|
||||
}
|
||||
|
||||
onClickGallery() {
|
||||
if (this.type === 'random') {
|
||||
this.router.navigate(['galleries', this.gallery.id]);
|
||||
}
|
||||
}
|
||||
|
||||
onClickImage(image) {
|
||||
if (this.type === 'full') {
|
||||
this.clicked.emit(image);
|
||||
}
|
||||
}
|
||||
|
||||
suiWidthForNumberPerRow(): string {
|
||||
switch (this.numberPerRow) {
|
||||
case 1: {
|
||||
return 'sixteen';
|
||||
}
|
||||
case 2: {
|
||||
return 'eight';
|
||||
}
|
||||
case 4: {
|
||||
return 'four';
|
||||
}
|
||||
default:
|
||||
return 'four';
|
||||
}
|
||||
}
|
||||
|
||||
setupFiles() {
|
||||
if (!this.gallery) { return; }
|
||||
|
||||
this.files = [...this.gallery.files];
|
||||
if (this.type === 'random') {
|
||||
this.shuffle(this.files);
|
||||
this.files = this.files.slice(0, this.numberOfRandomImages);
|
||||
} else if (this.type === 'gallery') {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: any) {
|
||||
if (!!changes.gallery) {
|
||||
this.setupFiles();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
<div
|
||||
(window:keydown)="onKey($event)"
|
||||
class="jwplayer">
|
||||
</div>
|
||||
@@ -1,139 +0,0 @@
|
||||
import { Component, EventEmitter, Input, Output, ElementRef } from '@angular/core';
|
||||
|
||||
declare var jwplayer: any;
|
||||
|
||||
@Component({
|
||||
selector: 'app-jwplayer',
|
||||
templateUrl: './jwplayer.component.html',
|
||||
styleUrls: ['./jwplayer.component.css']
|
||||
})
|
||||
export class JwplayerComponent {
|
||||
@Input() public title: string;
|
||||
@Input() public file: string;
|
||||
@Input() public image: string;
|
||||
@Input() public height: string;
|
||||
@Input() public width: string;
|
||||
@Output() public bufferChange: EventEmitter<any> = new EventEmitter();
|
||||
@Output() public complete: EventEmitter<any> = new EventEmitter();
|
||||
@Output() public buffer: EventEmitter<any> = new EventEmitter();
|
||||
@Output() public error: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() public play: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() public start: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() public fullscreen: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() public seeked: EventEmitter<any> = new EventEmitter();
|
||||
@Output() public time: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
private _player: any = null;
|
||||
|
||||
constructor(private _elementRef: ElementRef) { }
|
||||
|
||||
public get player(): any {
|
||||
this._player = this._player || jwplayer(this._elementRef.nativeElement);
|
||||
return this._player;
|
||||
}
|
||||
|
||||
public setupPlayer(file: string, image?: string, vtt?: string, chaptersVtt?: string) {
|
||||
this.player.remove();
|
||||
this.player.setup({
|
||||
file: file,
|
||||
image: image,
|
||||
tracks: [
|
||||
{
|
||||
file: vtt,
|
||||
kind: 'thumbnails'
|
||||
},
|
||||
{
|
||||
file: chaptersVtt,
|
||||
kind: 'chapters'
|
||||
}
|
||||
],
|
||||
primary: 'html5',
|
||||
autostart: false,
|
||||
playbackRateControls: [0.75, 1, 1.5, 2, 3, 4]
|
||||
});
|
||||
this.handleEventsFor(this.player);
|
||||
}
|
||||
|
||||
public handleEventsFor = (player: any) => {
|
||||
player.on('bufferChange', this.onBufferChange);
|
||||
player.on('buffer', this.onBuffer);
|
||||
player.on('complete', this.onComplete);
|
||||
player.on('error', this.onError);
|
||||
player.on('fullscreen', this.onFullScreen);
|
||||
player.on('play', this.onPlay);
|
||||
player.on('start', this.onStart);
|
||||
player.on('seeked', this.onSeeked);
|
||||
player.on('time', this.onTime);
|
||||
}
|
||||
|
||||
public onComplete = (options: {}) => this.complete.emit(options);
|
||||
|
||||
public onError = () => this.error.emit();
|
||||
|
||||
public onBufferChange = (options: {
|
||||
duration: number,
|
||||
bufferPercent: number,
|
||||
position: number,
|
||||
metadata?: number
|
||||
}) => this.bufferChange.emit(options)
|
||||
|
||||
public onBuffer = (options: {
|
||||
oldState: string,
|
||||
newState: string,
|
||||
reason: string
|
||||
}) => this.buffer.emit()
|
||||
|
||||
public onStart = (options: {
|
||||
oldState: string,
|
||||
newState: string,
|
||||
reason: string
|
||||
}) => this.buffer.emit()
|
||||
|
||||
public onFullScreen = (options: {
|
||||
oldState: string,
|
||||
newState: string,
|
||||
reason: string
|
||||
}) => this.buffer.emit()
|
||||
|
||||
public onPlay = (options: {
|
||||
}) => this.play.emit()
|
||||
|
||||
public onSeeked = (options: {
|
||||
}) => this.seeked.emit()
|
||||
|
||||
public onTime = (options: {
|
||||
duration: number,
|
||||
position: number,
|
||||
viewable: boolean
|
||||
}) => this.time.emit(options)
|
||||
|
||||
onKey(event) {
|
||||
const currentPlaybackRate = this._player.getPlaybackRate();
|
||||
switch (event.key) {
|
||||
|
||||
case '0': {
|
||||
this._player.setPlaybackRate(1);
|
||||
console.log(`Playback rate: 1`);
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
|
||||
case '2': {
|
||||
this._player.setPlaybackRate(currentPlaybackRate + 0.5);
|
||||
console.log(`Playback rate: ${currentPlaybackRate + 0.5}`);
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
|
||||
case '1': {
|
||||
this._player.setPlaybackRate(currentPlaybackRate - 0.5);
|
||||
console.log(`Playback rate: ${currentPlaybackRate - 0.5}`);
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
<div class="ui text menu">
|
||||
<div class="ui massive category search item" style="width: 33vw;">
|
||||
<div class="ui inverted transparent icon input" style="width: 100%; border-bottom: 1px rgba(0,0,0,0.1) solid;">
|
||||
<input [formControl]="searchFormControl" (ngModelChange)="onChange($event)" placeholder="Search..." type="text">
|
||||
<i class="search link icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right menu">
|
||||
<div class="ui item">
|
||||
<sui-select class="ui fluid selection"
|
||||
[(ngModel)]="filter.itemsPerPage"
|
||||
[options]="itemsPerPageOptions"
|
||||
(selectedOptionChange)="onPerPageChange($event)"
|
||||
placeholder="Items per page"
|
||||
#itemsPerPageSelect>
|
||||
<sui-select-option *ngFor="let option of itemsPerPageSelect.availableOptions" [value]="option"></sui-select-option>
|
||||
</sui-select>
|
||||
</div>
|
||||
<div class="ui item">
|
||||
<div class="ui buttons">
|
||||
<button
|
||||
*ngIf="filter.sortBy !== 'random'"
|
||||
(click)="onSortChange()"
|
||||
class="ui icon button">
|
||||
<i
|
||||
[class.up]="filter.sortDirection == 'asc'"
|
||||
[class.down]="filter.sortDirection == 'desc'"
|
||||
class="arrow icon"></i>
|
||||
</button>
|
||||
<div class="ui dropdown button" suiDropdown>
|
||||
<span class="text">{{filter.sortBy | capitalize}}</span>
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="menu" suiDropdownMenu>
|
||||
<div *ngFor="let item of filter.sortByOptions" class="item" (click)="onSortByChange(item)">{{item | capitalize}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui item">
|
||||
<button (click)="filter.criteriaFilterOpen = !filter.criteriaFilterOpen" class="ui icon button" [class.active]="filter.criteriaFilterOpen"><i class="filter icon"></i></button>
|
||||
</div>
|
||||
<div *ngIf="filter.displayModeOptions.length > 1" class="ui item">
|
||||
<div class="ui icon buttons">
|
||||
<button
|
||||
*ngIf="filter.displayModeOptions.includes(DisplayMode.Grid)"
|
||||
(click)="onModeChange(DisplayMode.Grid)"
|
||||
class="ui button"
|
||||
[class.active]="filter.displayMode == DisplayMode.Grid">
|
||||
<i class="grid layout icon"></i>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="filter.displayModeOptions.includes(DisplayMode.List)"
|
||||
(click)="onModeChange(DisplayMode.List)"
|
||||
class="ui button"
|
||||
[class.active]="filter.displayMode == DisplayMode.List">
|
||||
<i class="list layout icon"></i>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="filter.displayModeOptions.includes(DisplayMode.Wall)"
|
||||
(click)="onModeChange(DisplayMode.Wall)"
|
||||
class="ui button"
|
||||
[class.active]="filter.displayMode == DisplayMode.Wall">
|
||||
<i class="square full icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div [suiCollapse]="!filter.criteriaFilterOpen">
|
||||
<div class="ui basic segment">
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<div class="ui button" (click)="onAddCriteria()">Add Criteria</div>
|
||||
</div>
|
||||
<div *ngFor="let criteria of filter.criterions" class="fields">
|
||||
<div class="one wide field">
|
||||
<div class="ui button" (click)="onDeleteCriteria(criteria)">X</div>
|
||||
</div>
|
||||
<div class="three wide field">
|
||||
<sui-select
|
||||
class="selection"
|
||||
[(ngModel)]="criteria.type"
|
||||
[options]="filter.criteriaOptions"
|
||||
(selectedOptionChange)="onCriteriaTypeChange($event, criteria)"
|
||||
labelField="name"
|
||||
valueField="value"
|
||||
placeholder="Criteria"
|
||||
#criteriaSelect>
|
||||
<sui-select-option *ngFor="let option of criteriaSelect.availableOptions" [value]="option"></sui-select-option>
|
||||
</sui-select>
|
||||
</div>
|
||||
<div class="twelve wide field">
|
||||
<sui-select
|
||||
*ngIf="criteria.valueType == CriteriaValueType.Single"
|
||||
class="selection"
|
||||
[(ngModel)]="criteria.value"
|
||||
[options]="criteria.options"
|
||||
(selectedOptionChange)="onCriteriaValueChange($event)"
|
||||
[isDisabled]="criteria?.type == CriteriaType.None"
|
||||
placeholder="Criteria"
|
||||
#criteriaValueSelect>
|
||||
<sui-select-option *ngFor="let option of criteriaValueSelect.availableOptions" [value]="option"></sui-select-option>
|
||||
</sui-select>
|
||||
<sui-multi-select
|
||||
*ngIf="criteria.valueType == CriteriaValueType.Multiple"
|
||||
class="selection"
|
||||
[(ngModel)]="criteria.values"
|
||||
[options]="criteria.options"
|
||||
(selectedOptionsChange)="onCriteriaValueChange($event)"
|
||||
labelField="name"
|
||||
valueField="id"
|
||||
[isSearchable]="true"
|
||||
placeholder="Criteria"
|
||||
#criteriaValuesSelect>
|
||||
<sui-select-option *ngFor="let option of criteriaValuesSelect.availableOptions" [value]="option"></sui-select-option>
|
||||
</sui-multi-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,105 +0,0 @@
|
||||
import { Component, OnInit, OnDestroy, Input, Output, ViewChildren, EventEmitter, ElementRef, QueryList } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
|
||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
import { StashService } from '../../core/stash.service';
|
||||
import { ListFilter, DisplayMode, Criteria, CriteriaType, CriteriaValueType } from '../models/list-state.model';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-filter',
|
||||
templateUrl: './list-filter.component.html'
|
||||
})
|
||||
export class ListFilterComponent implements OnInit, OnDestroy {
|
||||
DisplayMode = DisplayMode;
|
||||
CriteriaType = CriteriaType;
|
||||
CriteriaValueType = CriteriaValueType;
|
||||
|
||||
@Input() filter: ListFilter;
|
||||
@Output() filterUpdated = new EventEmitter<ListFilter>();
|
||||
@ViewChildren('criteriaValueSelect') criteriaValueSelectInputs: QueryList<ElementRef>;
|
||||
@ViewChildren('criteriaValuesSelect') criteriaValuesSelectInputs: QueryList<ElementRef>;
|
||||
|
||||
itemsPerPageOptions = [20, 40, 60, 120];
|
||||
|
||||
searchFormControl = new FormControl();
|
||||
|
||||
constructor(private stashService: StashService, private route: ActivatedRoute) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.queryParams.subscribe(params => {
|
||||
this.filter.configureFromQueryParameters(params, this.stashService);
|
||||
if (params['q'] != null) {
|
||||
this.searchFormControl.setValue(this.filter.searchTerm);
|
||||
}
|
||||
});
|
||||
|
||||
this.filter.configureForFilterMode(this.filter.filterMode);
|
||||
this.searchFormControl.valueChanges
|
||||
.pipe(
|
||||
debounceTime(400),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
.subscribe(term => {
|
||||
this.filter.searchTerm = term;
|
||||
this.filterUpdated.emit(this.filter);
|
||||
});
|
||||
|
||||
this.filterUpdated.emit(this.filter);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
// this.searchFormControl.valueChanges.unsubscribe();
|
||||
}
|
||||
|
||||
onPerPageChange(perPage: number) {
|
||||
this.filterUpdated.emit(this.filter);
|
||||
}
|
||||
|
||||
onSortChange() {
|
||||
this.filter.sortDirection = this.filter.sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
this.filterUpdated.emit(this.filter);
|
||||
}
|
||||
|
||||
onSortByChange(sortBy: string) {
|
||||
this.filter.sortBy = sortBy;
|
||||
this.filterUpdated.emit(this.filter);
|
||||
}
|
||||
|
||||
onAddCriteria() {
|
||||
const criteria = new Criteria();
|
||||
this.filter.criterions.push(criteria);
|
||||
}
|
||||
|
||||
onDeleteCriteria(criteria: Criteria) {
|
||||
const idx = this.filter.criterions.indexOf(criteria);
|
||||
this.filter.criterions.splice(idx, 1);
|
||||
this.filterUpdated.emit(this.filter);
|
||||
}
|
||||
|
||||
onCriteriaTypeChange(criteriaType: CriteriaType, criteria: Criteria) {
|
||||
// if (!!this.criteriaValueSelect) {
|
||||
// this.criteriaValueSelect.selectedOption = null;
|
||||
// }
|
||||
// if (!!this.criteriaValuesSelect) {
|
||||
// this.criteriaValuesSelect.selectedOptions = null;
|
||||
// }
|
||||
criteria.configure(criteriaType, this.stashService);
|
||||
this.filterUpdated.emit(this.filter);
|
||||
}
|
||||
|
||||
onCriteriaValueChange(criteriaValue: string) {
|
||||
this.filterUpdated.emit(this.filter);
|
||||
}
|
||||
|
||||
onModeChange(mode: DisplayMode) {
|
||||
this.filter.displayMode = mode;
|
||||
this.filterUpdated.emit(this.filter);
|
||||
}
|
||||
|
||||
onChange(event) {
|
||||
// console.debug('filter change', this.filter);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
<div class="ui segement">
|
||||
<app-list-filter (filterUpdated)="onFilterUpdate($event)" [filter]="state.filter"></app-list-filter>
|
||||
|
||||
<div *ngIf="state.filter.displayMode == DisplayMode.Grid" class="ui dark four doubling cards">
|
||||
<div *ngIf="state.filter.filterMode == FilterMode.Scenes; then sceneCards"></div>
|
||||
<div *ngIf="state.filter.filterMode == FilterMode.Galleries; then galleryCards"></div>
|
||||
<div *ngIf="state.filter.filterMode == FilterMode.Performers; then performerCards"></div>
|
||||
<div *ngIf="state.filter.filterMode == FilterMode.Studios; then studioCards"></div>
|
||||
</div>
|
||||
<div *ngIf="state.filter.displayMode == DisplayMode.List" class="ui dark divided items">
|
||||
<div *ngIf="state.filter.filterMode == FilterMode.Scenes; then sceneListItems"></div>
|
||||
<div *ngIf="state.filter.filterMode == FilterMode.Galleries; then galleryListItems"></div>
|
||||
<div *ngIf="state.filter.filterMode == FilterMode.Performers; then performerListItems"></div>
|
||||
<div *ngIf="state.filter.filterMode == FilterMode.Studios; then studioListItems"></div>
|
||||
</div>
|
||||
<div *ngIf="state.filter.displayMode == DisplayMode.Wall" style="position: relative; left: calc(-50vw + 50.7%); width: 99.2vw;">
|
||||
<div class="ui five column grid" style="margin: 0;">
|
||||
<div *ngIf="state.filter.filterMode == FilterMode.Scenes; then sceneWallItems"></div>
|
||||
<div *ngIf="state.filter.filterMode == FilterMode.SceneMarkers; then sceneMarkerWallItems"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-sui-pagination #pagination id="main-pagination" (pageChange)="getPage($event)"></app-sui-pagination>
|
||||
|
||||
<div class="ui inverted dimmer" [class.active]="loading">
|
||||
<div class="ui centered large text loader">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #sceneCards>
|
||||
<app-scene-card *ngFor="let scene of state.data | paginate: { itemsPerPage: state.filter.itemsPerPage, currentPage: state.filter.currentPage, totalItems: state.totalCount }"
|
||||
[scene]="scene">
|
||||
</app-scene-card>
|
||||
</ng-template>
|
||||
<ng-template #sceneListItems>
|
||||
<app-scene-list-item *ngFor="let scene of state.data | paginate: { itemsPerPage: state.filter.itemsPerPage, currentPage: state.filter.currentPage, totalItems: state.totalCount }" [scene]="scene">
|
||||
</app-scene-list-item>
|
||||
</ng-template>
|
||||
<ng-template #sceneWallItems>
|
||||
<div
|
||||
*ngFor="let scene of state.data | paginate: { itemsPerPage: state.filter.itemsPerPage, currentPage: state.filter.currentPage, totalItems: state.totalCount }"
|
||||
class="wall column">
|
||||
<app-scene-wall-item [scene]="scene"></app-scene-wall-item>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #galleryCards>
|
||||
<app-gallery-card *ngFor="let gallery of state.data | paginate: { itemsPerPage: state.filter.itemsPerPage, currentPage: state.filter.currentPage, totalItems: state.totalCount }"
|
||||
[gallery]="gallery">
|
||||
</app-gallery-card>
|
||||
</ng-template>
|
||||
<ng-template #galleryListItems>
|
||||
<!--<app-gallery-list-item *ngFor="let gallery of state.data | paginate: { itemsPerPage: state.filter.itemsPerPage, currentPage: state.filter.currentPage, totalItems: state.totalCount }" [scene]="scene">
|
||||
</app-gallery-list-item>-->
|
||||
</ng-template>
|
||||
|
||||
<ng-template #performerCards>
|
||||
<app-performer-card *ngFor="let performer of state.data | paginate: { itemsPerPage: state.filter.itemsPerPage, currentPage: state.filter.currentPage, totalItems: state.totalCount }"
|
||||
[performer]="performer">
|
||||
</app-performer-card>
|
||||
</ng-template>
|
||||
<ng-template #performerListItems>
|
||||
<app-performer-list-item *ngFor="let performer of state.data | paginate: { itemsPerPage: state.filter.itemsPerPage, currentPage: state.filter.currentPage, totalItems: state.totalCount }" [performer]="performer">
|
||||
</app-performer-list-item>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #studioCards>
|
||||
<app-studio-card *ngFor="let studio of state.data | paginate: { itemsPerPage: state.filter.itemsPerPage, currentPage: state.filter.currentPage, totalItems: state.totalCount }"
|
||||
[studio]="studio">
|
||||
</app-studio-card>
|
||||
</ng-template>
|
||||
<ng-template #studioListItems>
|
||||
<!--<app-studio-list-item *ngFor="let studio of state.data | paginate: { itemsPerPage: state.filter.itemsPerPage, currentPage: state.filter.currentPage, totalItems: state.totalCount }" [scene]="scene">
|
||||
</app-studio-list-item>-->
|
||||
</ng-template>
|
||||
|
||||
<ng-template #sceneMarkerWallItems>
|
||||
<div
|
||||
*ngFor="let sceneMarker of state.data | paginate: { itemsPerPage: state.filter.itemsPerPage, currentPage: state.filter.currentPage, totalItems: state.totalCount }"
|
||||
class="wall column">
|
||||
<app-scene-marker-wall-item
|
||||
[sceneMarker]="sceneMarker">
|
||||
</app-scene-marker-wall-item>
|
||||
</div>
|
||||
</ng-template>
|
||||
@@ -1,95 +0,0 @@
|
||||
import { Component, OnInit, OnDestroy, Input, AfterViewInit } from '@angular/core';
|
||||
|
||||
import { StashService } from '../../core/stash.service';
|
||||
|
||||
import {
|
||||
DisplayMode,
|
||||
FilterMode,
|
||||
ListFilter,
|
||||
ListState,
|
||||
SceneListState,
|
||||
GalleryListState,
|
||||
PerformerListState,
|
||||
StudioListState,
|
||||
SceneMarkerListState
|
||||
} from '../../shared/models/list-state.model';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list',
|
||||
templateUrl: './list.component.html',
|
||||
styleUrls: ['./list.component.css']
|
||||
})
|
||||
export class ListComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
DisplayMode = DisplayMode;
|
||||
FilterMode = FilterMode;
|
||||
|
||||
@Input() state: ListState<any>;
|
||||
|
||||
loading = true;
|
||||
|
||||
constructor(private stashService: StashService,
|
||||
private router: Router,
|
||||
private activatedRoute: ActivatedRoute) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.loading = true;
|
||||
this.state.reset();
|
||||
this.state.scrollY = window.scrollY;
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
if (!!this.state.scrollY) {
|
||||
setTimeout(() => {
|
||||
window.scroll(0, this.state.scrollY);
|
||||
}, 1);
|
||||
} else {
|
||||
window.scroll(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
async getData() {
|
||||
this.loading = true;
|
||||
|
||||
if (this.state instanceof SceneListState) {
|
||||
const result = await this.stashService.findScenes(this.state.filter.currentPage, this.state.filter).result();
|
||||
this.state.data = result.data.findScenes.scenes;
|
||||
this.state.totalCount = result.data.findScenes.count;
|
||||
} else if (this.state instanceof GalleryListState) {
|
||||
const result = await this.stashService.findGalleries(this.state.filter.currentPage, this.state.filter).result();
|
||||
this.state.data = result.data.findGalleries.galleries;
|
||||
this.state.totalCount = result.data.findGalleries.count;
|
||||
} else if (this.state instanceof PerformerListState) {
|
||||
const result = await this.stashService.findPerformers(this.state.filter.currentPage, this.state.filter).result();
|
||||
this.state.data = result.data.findPerformers.performers;
|
||||
this.state.totalCount = result.data.findPerformers.count;
|
||||
} else if (this.state instanceof StudioListState) {
|
||||
const result = await this.stashService.findStudios(this.state.filter.currentPage, this.state.filter).result();
|
||||
this.state.data = result.data.findStudios.studios;
|
||||
this.state.totalCount = result.data.findStudios.count;
|
||||
} else if (this.state instanceof SceneMarkerListState) {
|
||||
const result = await this.stashService.findSceneMarkers(this.state.filter.currentPage, this.state.filter).result();
|
||||
this.state.data = result.data.findSceneMarkers.scene_markers;
|
||||
this.state.totalCount = result.data.findSceneMarkers.count;
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
onFilterUpdate(filter: ListFilter) {
|
||||
console.log('filter update', filter);
|
||||
const options = Object.assign({relativeTo: this.activatedRoute, replaceUrl: true}, filter.makeQueryParameters());
|
||||
this.router.navigate([], options);
|
||||
this.state.filter = filter;
|
||||
this.getData();
|
||||
}
|
||||
|
||||
getPage(page: number) {
|
||||
this.state.filter.currentPage = page;
|
||||
this.onFilterUpdate(this.state.filter);
|
||||
window.scroll(0, 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export class GalleryImage {
|
||||
index: number;
|
||||
name: string;
|
||||
path?: string;
|
||||
}
|
||||
@@ -1,426 +0,0 @@
|
||||
import {
|
||||
SceneFilterType,
|
||||
ResolutionEnum,
|
||||
PerformerFilterType,
|
||||
SceneMarkerFilterType,
|
||||
SlimSceneData,
|
||||
PerformerData,
|
||||
StudioData,
|
||||
GalleryData,
|
||||
SceneMarkerData
|
||||
} from '../../core/graphql-generated';
|
||||
|
||||
import { StashService } from '../../core/stash.service';
|
||||
|
||||
export enum DisplayMode {
|
||||
Grid,
|
||||
List,
|
||||
Wall
|
||||
}
|
||||
|
||||
export enum FilterMode {
|
||||
Scenes,
|
||||
Performers,
|
||||
Studios,
|
||||
Galleries,
|
||||
SceneMarkers
|
||||
}
|
||||
|
||||
export class CustomCriteria {
|
||||
key: string;
|
||||
value: string;
|
||||
constructor(key: string, value: string) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
export enum CriteriaType {
|
||||
None,
|
||||
Rating,
|
||||
Resolution,
|
||||
Favorite,
|
||||
HasMarkers,
|
||||
IsMissing,
|
||||
Tags,
|
||||
SceneTags,
|
||||
Performers
|
||||
}
|
||||
|
||||
export enum CriteriaValueType {
|
||||
Single,
|
||||
Multiple
|
||||
}
|
||||
|
||||
export class CriteriaOption {
|
||||
name: string;
|
||||
value: CriteriaType;
|
||||
constructor(type: CriteriaType, name: string = CriteriaType[type]) {
|
||||
this.name = name;
|
||||
this.value = type;
|
||||
}
|
||||
}
|
||||
|
||||
interface CriteriaConfig {
|
||||
valueType: CriteriaValueType;
|
||||
parameterName: string;
|
||||
options: any[];
|
||||
}
|
||||
|
||||
export class Criteria {
|
||||
type: CriteriaType;
|
||||
valueType: CriteriaValueType;
|
||||
options: any[] = [];
|
||||
parameterName: string;
|
||||
value: string;
|
||||
values: string[];
|
||||
|
||||
private stashService: StashService;
|
||||
|
||||
async configure(type: CriteriaType, stashService: StashService) {
|
||||
this.type = type;
|
||||
this.stashService = stashService;
|
||||
|
||||
let config: CriteriaConfig = {
|
||||
valueType: CriteriaValueType.Single,
|
||||
parameterName: '',
|
||||
options: []
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case CriteriaType.Rating:
|
||||
config.parameterName = 'rating';
|
||||
config.options = [1, 2, 3, 4, 5];
|
||||
break;
|
||||
case CriteriaType.Resolution:
|
||||
config.parameterName = 'resolution';
|
||||
config.options = ['240p', '480p', '720p', '1080p', '4k'];
|
||||
break;
|
||||
case CriteriaType.Favorite:
|
||||
config.parameterName = 'filter_favorites';
|
||||
config.options = ['true', 'false'];
|
||||
break;
|
||||
case CriteriaType.HasMarkers:
|
||||
config.parameterName = 'has_markers';
|
||||
config.options = ['true', 'false'];
|
||||
break;
|
||||
case CriteriaType.IsMissing:
|
||||
config.parameterName = 'is_missing';
|
||||
config.options = ['title', 'url', 'date', 'gallery', 'studio', 'performers'];
|
||||
break;
|
||||
case CriteriaType.Tags:
|
||||
config = await this.configureTags('tags');
|
||||
break;
|
||||
case CriteriaType.SceneTags:
|
||||
config = await this.configureTags('scene_tags');
|
||||
break;
|
||||
case CriteriaType.Performers:
|
||||
config = await this.configurePerformers('performers');
|
||||
break;
|
||||
case CriteriaType.None:
|
||||
default: break;
|
||||
}
|
||||
|
||||
this.valueType = config.valueType;
|
||||
this.parameterName = config.parameterName;
|
||||
this.options = config.options;
|
||||
|
||||
this.value = ''; // Need this or else we send invalid value to the new filter
|
||||
// this.values = []; // TODO this seems to break the "Multiple" filters
|
||||
}
|
||||
|
||||
private async configureTags(name: string) {
|
||||
const result = await this.stashService.allTagsForFilter().result();
|
||||
return {
|
||||
valueType: CriteriaValueType.Multiple,
|
||||
parameterName: name,
|
||||
options: result.data.allTags.map(item => {
|
||||
return { id: item.id, name: item.name };
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
private async configurePerformers(name: string) {
|
||||
const result = await this.stashService.allPerformersForFilter().result();
|
||||
return {
|
||||
valueType: CriteriaValueType.Multiple,
|
||||
parameterName: name,
|
||||
options: result.data.allPerformers.map(item => {
|
||||
return { id: item.id, name: item.name, image_path: item.image_path };
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class ListFilter {
|
||||
searchTerm?: string;
|
||||
performers?: number[];
|
||||
currentPage = 1;
|
||||
itemsPerPage = 40;
|
||||
sortDirection = 'asc';
|
||||
sortBy: string;
|
||||
displayModeOptions: DisplayMode[] = [];
|
||||
displayMode: DisplayMode;
|
||||
filterMode: FilterMode;
|
||||
sortByOptions: string[];
|
||||
criteriaFilterOpen = false;
|
||||
criteriaOptions: CriteriaOption[];
|
||||
criterions: Criteria[] = [];
|
||||
customCriteria: CustomCriteria[] = [];
|
||||
|
||||
configureForFilterMode(filterMode: FilterMode) {
|
||||
switch (filterMode) {
|
||||
case FilterMode.Scenes:
|
||||
if (!!this.sortBy === false) { this.sortBy = 'date'; }
|
||||
this.sortByOptions = ['title', 'rating', 'date', 'filesize', 'duration', 'framerate', 'bitrate', 'random'];
|
||||
this.displayModeOptions = [
|
||||
DisplayMode.Grid,
|
||||
DisplayMode.List,
|
||||
DisplayMode.Wall
|
||||
];
|
||||
this.criteriaOptions = [
|
||||
new CriteriaOption(CriteriaType.None),
|
||||
new CriteriaOption(CriteriaType.Rating),
|
||||
new CriteriaOption(CriteriaType.Resolution),
|
||||
new CriteriaOption(CriteriaType.HasMarkers),
|
||||
new CriteriaOption(CriteriaType.IsMissing),
|
||||
new CriteriaOption(CriteriaType.Tags)
|
||||
];
|
||||
break;
|
||||
case FilterMode.Performers:
|
||||
if (!!this.sortBy === false) { this.sortBy = 'name'; }
|
||||
this.sortByOptions = ['name', 'height', 'birthdate', 'scenes_count'];
|
||||
this.displayModeOptions = [
|
||||
DisplayMode.Grid,
|
||||
DisplayMode.List
|
||||
];
|
||||
this.criteriaOptions = [
|
||||
new CriteriaOption(CriteriaType.None),
|
||||
new CriteriaOption(CriteriaType.Favorite)
|
||||
];
|
||||
break;
|
||||
case FilterMode.Studios:
|
||||
if (!!this.sortBy === false) { this.sortBy = 'name'; }
|
||||
this.sortByOptions = ['name', 'scenes_count'];
|
||||
this.displayModeOptions = [
|
||||
DisplayMode.Grid
|
||||
];
|
||||
this.criteriaOptions = [
|
||||
new CriteriaOption(CriteriaType.None)
|
||||
];
|
||||
break;
|
||||
case FilterMode.Galleries:
|
||||
if (!!this.sortBy === false) { this.sortBy = 'title'; }
|
||||
this.sortByOptions = ['title', 'path'];
|
||||
this.displayModeOptions = [
|
||||
DisplayMode.Grid
|
||||
];
|
||||
this.criteriaOptions = [
|
||||
new CriteriaOption(CriteriaType.None)
|
||||
];
|
||||
break;
|
||||
case FilterMode.SceneMarkers:
|
||||
if (!!this.sortBy === false) { this.sortBy = 'title'; }
|
||||
this.sortByOptions = ['title', 'seconds', 'scene_id', 'random', 'scenes_updated_at'];
|
||||
this.displayModeOptions = [
|
||||
DisplayMode.Wall
|
||||
];
|
||||
this.criteriaOptions = [
|
||||
new CriteriaOption(CriteriaType.None),
|
||||
new CriteriaOption(CriteriaType.Tags),
|
||||
new CriteriaOption(CriteriaType.SceneTags),
|
||||
new CriteriaOption(CriteriaType.Performers)
|
||||
];
|
||||
break;
|
||||
default:
|
||||
this.sortByOptions = [];
|
||||
this.displayModeOptions = [];
|
||||
this.criteriaOptions = [new CriteriaOption(CriteriaType.None)];
|
||||
break;
|
||||
}
|
||||
if (!!this.displayMode === false) { this.displayMode = this.displayModeOptions[0]; }
|
||||
}
|
||||
|
||||
configureFromQueryParameters(params, stashService: StashService) {
|
||||
if (params['sortby'] != null) {
|
||||
this.sortBy = params['sortby'];
|
||||
}
|
||||
if (params['sortdir'] != null) {
|
||||
this.sortDirection = params['sortdir'];
|
||||
}
|
||||
if (params['disp'] != null) {
|
||||
this.displayMode = params['disp'];
|
||||
}
|
||||
if (params['q'] != null) {
|
||||
this.searchTerm = params['q'];
|
||||
}
|
||||
if (params['p'] != null) {
|
||||
this.currentPage = Number(params['p']);
|
||||
}
|
||||
|
||||
if (params['c'] != null) {
|
||||
this.criterions = [];
|
||||
|
||||
let jsonParameters: any[];
|
||||
if (params['c'] instanceof Array) {
|
||||
jsonParameters = params['c'];
|
||||
} else {
|
||||
jsonParameters = [params['c']];
|
||||
}
|
||||
|
||||
if (jsonParameters.length !== 0) {
|
||||
this.criteriaFilterOpen = true;
|
||||
}
|
||||
|
||||
jsonParameters.forEach(jsonString => {
|
||||
const encodedCriteria = JSON.parse(jsonString);
|
||||
const criteria = new Criteria();
|
||||
criteria.configure(encodedCriteria.type, stashService);
|
||||
if (criteria.valueType === CriteriaValueType.Single) {
|
||||
criteria.value = encodedCriteria.value;
|
||||
} else {
|
||||
criteria.values = encodedCriteria.values;
|
||||
}
|
||||
this.criterions.push(criteria);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
makeQueryParameters(): any {
|
||||
const encodedCriterion = [];
|
||||
this.criterions.forEach(criteria => {
|
||||
const encodedCriteria: any = {};
|
||||
encodedCriteria.type = criteria.type;
|
||||
if (criteria.valueType === CriteriaValueType.Single) {
|
||||
encodedCriteria.value = criteria.value;
|
||||
} else {
|
||||
encodedCriteria.values = criteria.values;
|
||||
}
|
||||
const jsonCriteria = JSON.stringify(encodedCriteria);
|
||||
encodedCriterion.push(jsonCriteria);
|
||||
});
|
||||
|
||||
const result = {
|
||||
queryParams: {
|
||||
sortby: this.sortBy,
|
||||
sortdir: this.sortDirection,
|
||||
disp: this.displayMode,
|
||||
q: this.searchTerm,
|
||||
p: this.currentPage,
|
||||
c: encodedCriterion
|
||||
},
|
||||
queryParamsHandling: 'merge'
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: These don't support multiple of the same criteria, only the last one set is used.
|
||||
|
||||
makeSceneFilter(): SceneFilterType {
|
||||
const result: SceneFilterType = {};
|
||||
this.criterions.forEach(criteria => {
|
||||
switch (criteria.type) {
|
||||
case CriteriaType.Rating:
|
||||
result.rating = Number(criteria.value);
|
||||
break;
|
||||
case CriteriaType.Resolution: {
|
||||
switch (criteria.value) {
|
||||
case '240p': result.resolution = ResolutionEnum.Low; break;
|
||||
case '480p': result.resolution = ResolutionEnum.Standard; break;
|
||||
case '720p': result.resolution = ResolutionEnum.StandardHd; break;
|
||||
case '1080p': result.resolution = ResolutionEnum.FullHd; break;
|
||||
case '4k': result.resolution = ResolutionEnum.FourK; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CriteriaType.HasMarkers:
|
||||
result.has_markers = criteria.value;
|
||||
break;
|
||||
case CriteriaType.IsMissing:
|
||||
result.is_missing = criteria.value;
|
||||
break;
|
||||
case CriteriaType.Tags:
|
||||
result.tags = criteria.values;
|
||||
break;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
makePerformerFilter(): PerformerFilterType {
|
||||
const result: PerformerFilterType = {};
|
||||
this.criterions.forEach(criteria => {
|
||||
switch (criteria.type) {
|
||||
case CriteriaType.Favorite:
|
||||
result.filter_favorites = criteria.value === 'true';
|
||||
break;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
makeSceneMarkerFilter(): SceneMarkerFilterType {
|
||||
const result: SceneMarkerFilterType = {};
|
||||
this.criterions.forEach(criteria => {
|
||||
switch (criteria.type) {
|
||||
case CriteriaType.Tags:
|
||||
result.tags = criteria.values;
|
||||
break;
|
||||
case CriteriaType.SceneTags:
|
||||
result.scene_tags = criteria.values;
|
||||
break;
|
||||
case CriteriaType.Performers:
|
||||
result.performers = criteria.values;
|
||||
break;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class ListState<T> {
|
||||
totalCount: number;
|
||||
scrollY: number;
|
||||
filter: ListFilter = new ListFilter();
|
||||
data: T[];
|
||||
|
||||
reset() {
|
||||
this.data = null;
|
||||
this.totalCount = null;
|
||||
}
|
||||
}
|
||||
|
||||
export class SceneListState extends ListState<SlimSceneData.Fragment> {
|
||||
constructor() {
|
||||
super();
|
||||
this.filter.filterMode = FilterMode.Scenes;
|
||||
}
|
||||
}
|
||||
|
||||
export class PerformerListState extends ListState<PerformerData.Fragment> {
|
||||
constructor() {
|
||||
super();
|
||||
this.filter.filterMode = FilterMode.Performers;
|
||||
}
|
||||
}
|
||||
|
||||
export class StudioListState extends ListState<StudioData.Fragment> {
|
||||
constructor() {
|
||||
super();
|
||||
this.filter.filterMode = FilterMode.Studios;
|
||||
}
|
||||
}
|
||||
|
||||
export class GalleryListState extends ListState<GalleryData.Fragment> {
|
||||
constructor() {
|
||||
super();
|
||||
this.filter.filterMode = FilterMode.Galleries;
|
||||
}
|
||||
}
|
||||
|
||||
export class SceneMarkerListState extends ListState<SceneMarkerData.Fragment> {
|
||||
constructor() {
|
||||
super();
|
||||
this.filter.filterMode = FilterMode.SceneMarkers;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<a [routerLink]="['/performers', performer.id]" class="performer image" [style.background-image]="'url(' + performer.image_path + ')'"></a>
|
||||
<div class="content">
|
||||
<div class="header">
|
||||
<div class="ui heart rating">
|
||||
<i class="icon" [class.active]="performer.favorite"></i>
|
||||
</div>
|
||||
{{performer.name}}
|
||||
</div>
|
||||
<div class="meta">
|
||||
<div *ngIf="!!performer.birthdate" class="item">
|
||||
<span *ngIf="!!ageFromDate" class="date">{{performer.birthdate | age: ageFromDate}} years old in this scene.</span>
|
||||
<span *ngIf="!!ageFromDate === false" class="date">{{performer.birthdate | age}} years old.</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span>Stars in {{performer.scene_count}} scenes.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Component, OnInit, Input, HostBinding } from '@angular/core';
|
||||
|
||||
import { PerformerData } from '../../core/graphql-generated';
|
||||
|
||||
@Component({
|
||||
selector: 'app-performer-card',
|
||||
templateUrl: './performer-card.component.html',
|
||||
styleUrls: ['./performer-card.component.css']
|
||||
})
|
||||
export class PerformerCardComponent implements OnInit {
|
||||
@Input() performer: PerformerData.Fragment;
|
||||
@Input() ageFromDate: string;
|
||||
|
||||
// The host class needs to be card
|
||||
@HostBinding('class') class = 'dark card';
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<a [routerLink]="['/performers', performer.id]" class="image previewable" style="width: 75px;">
|
||||
<img [src]="performer.image_path" />
|
||||
</a>
|
||||
<div class="content">
|
||||
<div class="ui header">
|
||||
<div class="ui heart rating">
|
||||
<i class="icon" [class.active]="performer.favorite"></i>
|
||||
</div>
|
||||
{{performer.name}} <span *ngIf="performer.birthdate">- {{performer.birthdate | age}}</span>
|
||||
<span *ngIf="performer.aliases" class="sub header">({{performer.aliases}})</span>
|
||||
</div>
|
||||
<div *ngIf="performer.country" class="meta">Country: {{performer.country}}</div>
|
||||
<div *ngIf="performer.measurements" class="meta">Measurements: {{performer.measurements}}</div>
|
||||
|
||||
<div *ngIf="performer.scene_count > 0" class="meta">
|
||||
<button class="ui inverted mini basic button" (click)="toggleScenes()">
|
||||
{{performer.scene_count}} scenes
|
||||
</button>
|
||||
</div>
|
||||
<div [suiCollapse]="!showingScenes">
|
||||
<div class="extra content">
|
||||
<div class="header">Sample Scenes</div>
|
||||
<div class="ui equal width grid" style="margin: 0; align-items: center; overflow: hidden;">
|
||||
<div *ngFor="let scene of scenes" class="column" style="padding: 0;">
|
||||
<a [routerLink]="['/scenes', scene.id]">
|
||||
<img class="ui image" style="max-height: 100%;" [src]="scene.paths.webp" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user