commit
8a2c10a061
@ -1,52 +1,52 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mediarepo-api"
|
name = "mediarepo-api"
|
||||||
version = "0.28.0"
|
version = "0.28.1"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "gpl-3"
|
license = "gpl-3"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tracing = "^0.1.29"
|
tracing = "0.1.30"
|
||||||
thiserror = "^1.0.30"
|
thiserror = "1.0.30"
|
||||||
async-trait = {version = "^0.1.52", optional=true}
|
async-trait = { version = "0.1.52", optional = true }
|
||||||
parking_lot = {version="^0.11.2", optional=true}
|
parking_lot = { version = "0.12.0", optional = true }
|
||||||
serde_json = {version="^1.0.73", optional=true}
|
serde_json = { version = "1.0.78", optional = true }
|
||||||
directories = {version="^4.0.1", optional=true}
|
directories = { version = "4.0.1", optional = true }
|
||||||
mime_guess = {version = "^2.0.3", optional=true}
|
mime_guess = { version = "2.0.3", optional = true }
|
||||||
serde_piecewise_default = "^0.2.0"
|
serde_piecewise_default = "0.2.0"
|
||||||
futures = {version = "^0.3.19", optional=true}
|
futures = { version = "0.3.19", optional = true }
|
||||||
url = {version = "^2.2.2", optional=true }
|
url = { version = "2.2.2", optional = true }
|
||||||
pathsearch = {version="^0.2.0", optional=true}
|
pathsearch = { version = "0.2.0", optional = true }
|
||||||
|
|
||||||
[dependencies.bromine]
|
[dependencies.bromine]
|
||||||
version = "^0.17.1"
|
version = "0.17.1"
|
||||||
optional = true
|
optional = true
|
||||||
features = ["serialize_bincode"]
|
features = ["serialize_bincode"]
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "^1.0.132"
|
version = "1.0.136"
|
||||||
features = ["serde_derive"]
|
features = ["serde_derive"]
|
||||||
|
|
||||||
[dependencies.chrono]
|
[dependencies.chrono]
|
||||||
version = "^0.4.19"
|
version = "0.4.19"
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
|
||||||
[dependencies.tauri]
|
[dependencies.tauri]
|
||||||
version = "^1.0.0-beta.8"
|
version = "1.0.0-beta.8"
|
||||||
optional=true
|
optional=true
|
||||||
default-features = false
|
default-features = false
|
||||||
features = []
|
features = []
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
version = "^1.15.0"
|
version = "1.16.1"
|
||||||
optional = true
|
optional = true
|
||||||
features = ["sync", "fs", "net", "io-util", "io-std", "time", "rt", "process"]
|
features = ["sync", "fs", "net", "io-util", "io-std", "time", "rt", "process"]
|
||||||
|
|
||||||
[dependencies.toml]
|
[dependencies.toml]
|
||||||
version = "^0.5.8"
|
version = "0.5.8"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
tauri-plugin = ["client-api","tauri", "parking_lot", "serde_json", "tokio", "toml", "directories", "mime_guess", "futures", "url"]
|
tauri-plugin = ["client-api","tauri", "parking_lot", "serde_json", "tokio", "toml", "directories", "mime_guess", "futures", "url"]
|
||||||
client-api = ["bromine", "async-trait", "tokio", "pathsearch"]
|
client-api = ["bromine", "async-trait", "tokio", "pathsearch"]
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
use bromine::prelude::*;
|
||||||
|
use crate::client_api::error::ApiResult;
|
||||||
|
use crate::types::filtering::{SortingPreset, SortKey};
|
||||||
|
use super::IPCApi;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PresetApi {
|
||||||
|
ctx: PooledContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IPCApi for PresetApi {
|
||||||
|
fn namespace() -> &'static str {
|
||||||
|
"presets"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ctx(&self) -> PoolGuard<Context> {
|
||||||
|
self.ctx.acquire()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PresetApi {
|
||||||
|
pub fn new(ctx: PooledContext) -> Self {
|
||||||
|
Self { ctx }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all sorting presets of the repository
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn all_sorting_presets(&self) -> ApiResult<Vec<SortingPreset>> {
|
||||||
|
self.emit_and_get(
|
||||||
|
"all_sorting_presets",
|
||||||
|
(),
|
||||||
|
Some(Duration::from_secs(1))
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new sorting preset with the given keys
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn add_sorting_preset(&self, keys: Vec<SortKey>) -> ApiResult<SortingPreset> {
|
||||||
|
self.emit_and_get(
|
||||||
|
"add_sorting_preset",
|
||||||
|
keys,
|
||||||
|
Some(Duration::from_secs(1))
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes a given sorting preset by id
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn delete_sorting_preset(&self, id: i32) -> ApiResult<()> {
|
||||||
|
self.emit_and_get("delete_sorting_preset", id, Some(Duration::from_secs(1))).await
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
use crate::tauri_plugin::commands::ApiAccess;
|
||||||
|
use crate::tauri_plugin::error::PluginResult;
|
||||||
|
use crate::types::filtering::{SortingPreset, SortKey};
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn all_sorting_presets(api_state: ApiAccess<'_>) -> PluginResult<Vec<SortingPreset>> {
|
||||||
|
let api = api_state.api().await?;
|
||||||
|
let presets = api.preset.all_sorting_presets().await?;
|
||||||
|
|
||||||
|
Ok(presets)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn add_sorting_preset(api_state: ApiAccess<'_>, sort_keys: Vec<SortKey>) -> PluginResult<SortingPreset> {
|
||||||
|
let api = api_state.api().await?;
|
||||||
|
let preset = api.preset.add_sorting_preset(sort_keys).await?;
|
||||||
|
|
||||||
|
Ok(preset)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn delete_sorting_preset(api_state: ApiAccess<'_>, id: i32) -> PluginResult<()> {
|
||||||
|
let api = api_state.api().await?;
|
||||||
|
api.preset.delete_sorting_preset(id).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
-- Add migration script here
|
||||||
|
CREATE TABLE sorting_presets (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE sort_keys (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
key_type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
ascending INTEGER NOT NULL CHECK (ascending IN (0, 1)),
|
||||||
|
value VARCHAR(128)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE sorting_preset_keys (
|
||||||
|
preset_id INTEGER REFERENCES sorting_presets (id) ON DELETE CASCADE,
|
||||||
|
key_id INTEGER REFERENCES sort_keys (id),
|
||||||
|
key_index INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (preset_id, key_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX sorting_preset_index ON sorting_preset_keys (preset_id);
|
@ -0,0 +1,17 @@
|
|||||||
|
PRAGMA foreign_keys= off;
|
||||||
|
ALTER TABLE sorting_preset_keys
|
||||||
|
RENAME TO _sorting_preset_keys_old;
|
||||||
|
|
||||||
|
CREATE TABLE sorting_preset_keys
|
||||||
|
(
|
||||||
|
preset_id INTEGER REFERENCES sorting_presets (id) ON DELETE CASCADE,
|
||||||
|
key_id INTEGER REFERENCES sort_keys (id),
|
||||||
|
key_index INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (preset_id, key_id, key_index)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO sorting_preset_keys SELECT * FROM _sorting_preset_keys_old;
|
||||||
|
|
||||||
|
DROP TABLE _sorting_preset_keys_old;
|
||||||
|
|
||||||
|
PRAGMA foreign_keys= on;
|
@ -0,0 +1,26 @@
|
|||||||
|
use sea_orm::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "sort_keys")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
pub key_type: i32,
|
||||||
|
pub ascending: bool,
|
||||||
|
pub value: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl Related<super::sorting_preset::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
super::sorting_preset_key::Relation::SortingPreset.def()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn via() -> Option<RelationDef> {
|
||||||
|
Some(super::sorting_preset_key::Relation::SortingKey.def().rev())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
@ -0,0 +1,27 @@
|
|||||||
|
use sea_orm::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "sorting_presets")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl Related<super::sort_key::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
super::sorting_preset_key::Relation::SortingKey.def()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn via() -> Option<RelationDef> {
|
||||||
|
Some(
|
||||||
|
super::sorting_preset_key::Relation::SortingPreset
|
||||||
|
.def()
|
||||||
|
.rev(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
@ -0,0 +1,45 @@
|
|||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "sorting_preset_keys")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
preset_id: i32,
|
||||||
|
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
key_id: i32,
|
||||||
|
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
key_index: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::sorting_preset::Entity"
|
||||||
|
from = "Column::PresetId",
|
||||||
|
to = "super::sorting_preset::Column::Id"
|
||||||
|
)]
|
||||||
|
SortingPreset,
|
||||||
|
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::sort_key::Entity",
|
||||||
|
from = "Column::KeyId",
|
||||||
|
to = "super::sort_key::Column::Id"
|
||||||
|
)]
|
||||||
|
SortingKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::sorting_preset::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::SortingPreset.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::sort_key::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::SortingKey.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
@ -1,20 +1,6 @@
|
|||||||
|
use crate::dao_provider;
|
||||||
|
|
||||||
pub mod migrate_content_descriptors;
|
pub mod migrate_content_descriptors;
|
||||||
pub mod sqlite_operations;
|
pub mod sqlite_operations;
|
||||||
|
|
||||||
use crate::dao::{DaoContext, DaoProvider};
|
dao_provider!(JobDao);
|
||||||
|
|
||||||
pub struct JobDao {
|
|
||||||
ctx: DaoContext,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DaoProvider for JobDao {
|
|
||||||
fn dao_ctx(&self) -> DaoContext {
|
|
||||||
self.ctx.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JobDao {
|
|
||||||
pub fn new(ctx: DaoContext) -> JobDao {
|
|
||||||
Self { ctx }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,172 @@
|
|||||||
|
use crate::dao::sorting_preset::SortingPresetDao;
|
||||||
|
use crate::dto::{AddSortKeyDto, AddSortingPresetDto, SortKeyDto, SortingPresetDto};
|
||||||
|
use mediarepo_core::error::RepoResult;
|
||||||
|
use mediarepo_database::entities::{sort_key, sorting_preset, sorting_preset_key};
|
||||||
|
use sea_orm::prelude::*;
|
||||||
|
use sea_orm::ActiveValue::Set;
|
||||||
|
use sea_orm::{
|
||||||
|
Condition, ConnectionTrait, DatabaseTransaction, DbBackend, FromQueryResult, JoinType,
|
||||||
|
QuerySelect, Statement,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use sea_orm::TryGetableMany; // otherwise intellijrust hates on me
|
||||||
|
|
||||||
|
impl SortingPresetDao {
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn add(&self, preset: AddSortingPresetDto) -> RepoResult<SortingPresetDto> {
|
||||||
|
let trx = self.ctx.db.begin().await?;
|
||||||
|
let keys = add_keys(&trx, preset.keys).await?;
|
||||||
|
let key_ids = keys
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, key)| (idx, key.id()))
|
||||||
|
.collect::<Vec<(usize, i32)>>();
|
||||||
|
let condition = key_ids
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(create_mapping_condition)
|
||||||
|
.fold(Condition::any(), |acc, cond| acc.add(cond));
|
||||||
|
let existing_preset: Option<sorting_preset::Model> = sorting_preset::Entity::find()
|
||||||
|
.join(
|
||||||
|
JoinType::InnerJoin,
|
||||||
|
sorting_preset_key::Relation::SortingPreset.def().rev(),
|
||||||
|
)
|
||||||
|
.filter(condition)
|
||||||
|
.one(&trx)
|
||||||
|
.await?;
|
||||||
|
if let Some(model) = existing_preset {
|
||||||
|
trx.commit().await?;
|
||||||
|
return Ok(SortingPresetDto::new(model, keys));
|
||||||
|
}
|
||||||
|
|
||||||
|
// sea_orm currently doesn't support all-default-value inserts.
|
||||||
|
// TODOD: Replace after the change for default inserts has been merged
|
||||||
|
let preset_model = sorting_preset::Model::find_by_statement(Statement::from_string(
|
||||||
|
DbBackend::Sqlite,
|
||||||
|
"INSERT INTO sorting_presets DEFAULT VALUES RETURNING *;".to_string(),
|
||||||
|
))
|
||||||
|
.one(&trx)
|
||||||
|
.await?
|
||||||
|
.expect("failed to insert new sorting preset");
|
||||||
|
|
||||||
|
let mapping_models = key_ids
|
||||||
|
.into_iter()
|
||||||
|
.map(|(idx, key)| sorting_preset_key::ActiveModel {
|
||||||
|
preset_id: Set(preset_model.id),
|
||||||
|
key_id: Set(key),
|
||||||
|
key_index: Set(idx as i32),
|
||||||
|
})
|
||||||
|
.collect::<Vec<sorting_preset_key::ActiveModel>>();
|
||||||
|
|
||||||
|
if !mapping_models.is_empty() {
|
||||||
|
sorting_preset_key::Entity::insert_many(mapping_models)
|
||||||
|
.exec(&trx)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
trx.commit().await?;
|
||||||
|
|
||||||
|
Ok(SortingPresetDto::new(preset_model, keys))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_keys(
|
||||||
|
trx: &DatabaseTransaction,
|
||||||
|
keys: Vec<AddSortKeyDto>,
|
||||||
|
) -> RepoResult<Vec<SortKeyDto>> {
|
||||||
|
let mut key_dtos = find_sort_keys(trx, &keys).await?;
|
||||||
|
let mut insert_keys = keys.clone();
|
||||||
|
|
||||||
|
key_dtos.iter().for_each(|key| {
|
||||||
|
insert_keys.retain(|k| {
|
||||||
|
k.ascending != key.ascending()
|
||||||
|
|| k.key_type != key.key_type().unwrap()
|
||||||
|
|| !compare_opts_eq(key.value(), k.value.as_ref())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if !insert_keys.is_empty() {
|
||||||
|
let active_models: Vec<sort_key::ActiveModel> = insert_keys
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|key| sort_key::ActiveModel {
|
||||||
|
key_type: Set(key.key_type.to_number()),
|
||||||
|
ascending: Set(key.ascending),
|
||||||
|
value: Set(key.value),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
sort_key::Entity::insert_many(active_models)
|
||||||
|
.exec(trx)
|
||||||
|
.await?;
|
||||||
|
let mut new_keys = find_sort_keys(trx, &insert_keys).await?;
|
||||||
|
key_dtos.append(&mut new_keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
let keys_original_order = keys
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|k| {
|
||||||
|
key_dtos
|
||||||
|
.iter()
|
||||||
|
.find(|key| {
|
||||||
|
k.ascending == key.ascending()
|
||||||
|
&& k.key_type == key.key_type().unwrap()
|
||||||
|
&& compare_opts_eq(key.value(), k.value.as_ref())
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
})
|
||||||
|
.collect::<Vec<SortKeyDto>>();
|
||||||
|
|
||||||
|
Ok(keys_original_order)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn find_sort_keys(
|
||||||
|
trx: &DatabaseTransaction,
|
||||||
|
keys: &Vec<AddSortKeyDto>,
|
||||||
|
) -> RepoResult<Vec<SortKeyDto>> {
|
||||||
|
if keys.is_empty() {
|
||||||
|
return Ok(vec![]);
|
||||||
|
}
|
||||||
|
let condition = keys
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(create_sort_key_condition)
|
||||||
|
.fold(Condition::any(), |acc, cond| acc.add(cond));
|
||||||
|
let keys = sort_key::Entity::find()
|
||||||
|
.filter(condition)
|
||||||
|
.all(trx)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(SortKeyDto::new)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_sort_key_condition(key: AddSortKeyDto) -> Condition {
|
||||||
|
let mut condition = Condition::all()
|
||||||
|
.add(sort_key::Column::KeyType.eq(key.key_type.to_number()))
|
||||||
|
.add(sort_key::Column::Ascending.eq(key.ascending));
|
||||||
|
|
||||||
|
if let Some(value) = key.value {
|
||||||
|
condition = condition.add(sort_key::Column::Value.eq(value))
|
||||||
|
} else {
|
||||||
|
condition = condition.add(sort_key::Column::Value.is_null())
|
||||||
|
}
|
||||||
|
|
||||||
|
condition
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_mapping_condition(entry: (usize, i32)) -> Condition {
|
||||||
|
Condition::all()
|
||||||
|
.add(sorting_preset_key::Column::KeyId.eq(entry.1))
|
||||||
|
.add(sorting_preset_key::Column::KeyIndex.eq(entry.0 as i32))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_opts_eq<T: Eq>(opt1: Option<T>, opt2: Option<T>) -> bool {
|
||||||
|
if let (Some(opt1), Some(opt2)) = (&opt1, &opt2) {
|
||||||
|
opt1 == opt2
|
||||||
|
} else {
|
||||||
|
opt1.is_none() && opt2.is_none()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
pub mod add;
|
||||||
|
|
||||||
|
use crate::dao_provider;
|
||||||
|
use crate::dto::{SortKeyDto, SortingPresetDto};
|
||||||
|
use mediarepo_core::error::RepoResult;
|
||||||
|
use mediarepo_database::entities::{sort_key, sorting_preset, sorting_preset_key};
|
||||||
|
use sea_orm::prelude::*;
|
||||||
|
use sea_orm::QueryOrder;
|
||||||
|
|
||||||
|
dao_provider!(SortingPresetDao);
|
||||||
|
|
||||||
|
impl SortingPresetDao {
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn all(&self) -> RepoResult<Vec<SortingPresetDto>> {
|
||||||
|
let presets = sorting_preset::Entity::find()
|
||||||
|
.find_with_related(sort_key::Entity)
|
||||||
|
.order_by_asc(sorting_preset_key::Column::KeyIndex)
|
||||||
|
.all(&self.ctx.db)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(map_sorting_preset_dto)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(presets)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn delete(&self, id: i32) -> RepoResult<()> {
|
||||||
|
sorting_preset::Entity::delete_many()
|
||||||
|
.filter(sorting_preset::Column::Id.eq(id))
|
||||||
|
.exec(&self.ctx.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_sorting_preset_dto(
|
||||||
|
entry: (sorting_preset::Model, Vec<sort_key::Model>),
|
||||||
|
) -> SortingPresetDto {
|
||||||
|
SortingPresetDto::new(entry.0, entry.1.into_iter().map(SortKeyDto::new).collect())
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
use crate::dto::KeyType::{
|
||||||
|
FileChangeTime, FileCreatedTime, FileImportedTime, FileName, FileSize, FileType, Namespace,
|
||||||
|
NumTags,
|
||||||
|
};
|
||||||
|
use mediarepo_database::entities::sort_key;
|
||||||
|
use mediarepo_database::entities::sorting_preset;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SortingPresetDto {
|
||||||
|
model: sorting_preset::Model,
|
||||||
|
keys: Vec<SortKeyDto>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SortingPresetDto {
|
||||||
|
pub fn new(model: sorting_preset::Model, keys: Vec<SortKeyDto>) -> Self {
|
||||||
|
Self { model, keys }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> i32 {
|
||||||
|
self.model.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keys(&self) -> &Vec<SortKeyDto> {
|
||||||
|
&self.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_keys(self) -> Vec<SortKeyDto> {
|
||||||
|
self.keys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SortKeyDto {
|
||||||
|
model: sort_key::Model,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SortKeyDto {
|
||||||
|
pub fn new(model: sort_key::Model) -> Self {
|
||||||
|
Self { model }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> i32 {
|
||||||
|
self.model.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn key_type(&self) -> Option<KeyType> {
|
||||||
|
KeyType::from_number(self.model.key_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ascending(&self) -> bool {
|
||||||
|
self.model.ascending
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value(&self) -> Option<&String> {
|
||||||
|
self.model.value.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
|
||||||
|
pub enum KeyType {
|
||||||
|
Namespace = 0,
|
||||||
|
FileName = 1,
|
||||||
|
FileSize = 2,
|
||||||
|
FileImportedTime = 3,
|
||||||
|
FileCreatedTime = 4,
|
||||||
|
FileChangeTime = 5,
|
||||||
|
FileType = 6,
|
||||||
|
NumTags = 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyType {
|
||||||
|
pub fn from_number(number: i32) -> Option<KeyType> {
|
||||||
|
match number {
|
||||||
|
0 => Some(Namespace),
|
||||||
|
1 => Some(FileName),
|
||||||
|
2 => Some(FileSize),
|
||||||
|
3 => Some(FileImportedTime),
|
||||||
|
4 => Some(FileCreatedTime),
|
||||||
|
5 => Some(FileChangeTime),
|
||||||
|
6 => Some(FileType),
|
||||||
|
7 => Some(NumTags),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_number(&self) -> i32 {
|
||||||
|
self.clone() as i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AddSortingPresetDto {
|
||||||
|
pub keys: Vec<AddSortKeyDto>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AddSortKeyDto {
|
||||||
|
pub key_type: KeyType,
|
||||||
|
pub ascending: bool,
|
||||||
|
pub value: Option<String>,
|
||||||
|
}
|
@ -0,0 +1,118 @@
|
|||||||
|
use crate::from_model::FromModel;
|
||||||
|
use crate::utils::get_repo_from_context;
|
||||||
|
use mediarepo_core::bromine::prelude::*;
|
||||||
|
use mediarepo_core::mediarepo_api::types::filtering::{SortDirection, SortKey, SortingPreset};
|
||||||
|
use mediarepo_logic::dao::DaoProvider;
|
||||||
|
use mediarepo_logic::dto::{AddSortKeyDto, AddSortingPresetDto, KeyType};
|
||||||
|
|
||||||
|
pub struct PresetsNamespace;
|
||||||
|
|
||||||
|
impl NamespaceProvider for PresetsNamespace {
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"presets"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register(handler: &mut EventHandler) {
|
||||||
|
events!(handler,
|
||||||
|
"all_sorting_presets" => Self::all_sorting_presets,
|
||||||
|
"add_sorting_preset" => Self::add_sorting_preset,
|
||||||
|
"delete_sorting_preset" => Self::delete_sorting_preset
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PresetsNamespace {
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn all_sorting_presets(ctx: &Context, _: Event) -> IPCResult<()> {
|
||||||
|
let repo = get_repo_from_context(ctx).await;
|
||||||
|
let sorting_presets: Vec<SortingPreset> = repo
|
||||||
|
.sorting_preset()
|
||||||
|
.all()
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(SortingPreset::from_model)
|
||||||
|
.collect();
|
||||||
|
ctx.emit_to(Self::name(), "all_sorting_presets", sorting_presets)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn add_sorting_preset(ctx: &Context, event: Event) -> IPCResult<()> {
|
||||||
|
let keys = event
|
||||||
|
.payload::<Vec<SortKey>>()?
|
||||||
|
.into_iter()
|
||||||
|
.map(sort_key_to_add_dto)
|
||||||
|
.collect();
|
||||||
|
let repo = get_repo_from_context(ctx).await;
|
||||||
|
let preset = repo
|
||||||
|
.sorting_preset()
|
||||||
|
.add(AddSortingPresetDto { keys })
|
||||||
|
.await?;
|
||||||
|
ctx.emit_to(
|
||||||
|
Self::name(),
|
||||||
|
"add_sorting_preset",
|
||||||
|
SortingPreset::from_model(preset),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn delete_sorting_preset(ctx: &Context, event: Event) -> IPCResult<()> {
|
||||||
|
let id = event.payload::<i32>()?;
|
||||||
|
let repo = get_repo_from_context(ctx).await;
|
||||||
|
repo.sorting_preset().delete(id).await?;
|
||||||
|
ctx.emit_to(Self::name(), "delete_sorting_preset", ())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_key_to_add_dto(key: SortKey) -> AddSortKeyDto {
|
||||||
|
match key {
|
||||||
|
SortKey::Namespace(namespace) => AddSortKeyDto {
|
||||||
|
ascending: namespace.direction == SortDirection::Ascending,
|
||||||
|
key_type: KeyType::Namespace,
|
||||||
|
value: Some(namespace.name),
|
||||||
|
},
|
||||||
|
SortKey::FileName(dir) => AddSortKeyDto {
|
||||||
|
ascending: dir == SortDirection::Ascending,
|
||||||
|
key_type: KeyType::FileName,
|
||||||
|
value: None,
|
||||||
|
},
|
||||||
|
SortKey::FileSize(dir) => AddSortKeyDto {
|
||||||
|
ascending: dir == SortDirection::Ascending,
|
||||||
|
key_type: KeyType::FileSize,
|
||||||
|
value: None,
|
||||||
|
},
|
||||||
|
SortKey::FileImportedTime(dir) => AddSortKeyDto {
|
||||||
|
ascending: dir == SortDirection::Ascending,
|
||||||
|
key_type: KeyType::FileImportedTime,
|
||||||
|
value: None,
|
||||||
|
},
|
||||||
|
SortKey::FileCreatedTime(dir) => AddSortKeyDto {
|
||||||
|
ascending: dir == SortDirection::Ascending,
|
||||||
|
key_type: KeyType::FileCreatedTime,
|
||||||
|
value: None,
|
||||||
|
},
|
||||||
|
SortKey::FileChangeTime(dir) => AddSortKeyDto {
|
||||||
|
ascending: dir == SortDirection::Ascending,
|
||||||
|
key_type: KeyType::FileChangeTime,
|
||||||
|
value: None,
|
||||||
|
},
|
||||||
|
SortKey::FileType(dir) => AddSortKeyDto {
|
||||||
|
ascending: dir == SortDirection::Ascending,
|
||||||
|
key_type: KeyType::FileType,
|
||||||
|
value: None,
|
||||||
|
},
|
||||||
|
SortKey::NumTags(dir) => AddSortKeyDto {
|
||||||
|
ascending: dir == SortDirection::Ascending,
|
||||||
|
key_type: KeyType::NumTags,
|
||||||
|
value: None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import {SortKeyData} from "./files";
|
||||||
|
|
||||||
|
export type SortingPresetData = {
|
||||||
|
id: number,
|
||||||
|
keys: SortKeyData[],
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
import {SortDirection, SortKeyData} from "../api-types/files";
|
||||||
|
|
||||||
|
export type SortType =
|
||||||
|
"Namespace"
|
||||||
|
| "FileName"
|
||||||
|
| "FileSize"
|
||||||
|
| "FileImportedTime"
|
||||||
|
| "FileCreatedTime"
|
||||||
|
| "FileChangeTime"
|
||||||
|
| "FileType"
|
||||||
|
| "NumTags";
|
||||||
|
|
||||||
|
export class SortKey {
|
||||||
|
|
||||||
|
constructor(private data: SortKeyData) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get sortType(): SortType {
|
||||||
|
return Reflect.ownKeys(this.data)[0] as SortType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set sortType(value: SortType) {
|
||||||
|
if (value == "Namespace") {
|
||||||
|
this.data = {
|
||||||
|
Namespace: {
|
||||||
|
direction: this.sortDirection,
|
||||||
|
name: ""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.data = {
|
||||||
|
[value]: this.sortDirection
|
||||||
|
} as SortKeyData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get sortDirection(): SortDirection {
|
||||||
|
if ("Namespace" in this.data) {
|
||||||
|
return this.data.Namespace.direction;
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
return this.data[this.sortType];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public set sortDirection(value: SortDirection) {
|
||||||
|
const sortType = this.sortType;
|
||||||
|
if ("Namespace" in this.data) {
|
||||||
|
this.data.Namespace.direction = value;
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
this.data[this.sortType] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get namespaceName(): string | undefined {
|
||||||
|
if ("Namespace" in this.data) {
|
||||||
|
return this.data.Namespace.name;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set namespaceName(value: string | undefined) {
|
||||||
|
if (value && "Namespace" in this.data) {
|
||||||
|
this.data.Namespace.name = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get rawData(): SortKeyData {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromValues(
|
||||||
|
sortType: SortType,
|
||||||
|
sortDirection: SortDirection,
|
||||||
|
namespaceName: string | undefined
|
||||||
|
) {
|
||||||
|
let data;
|
||||||
|
|
||||||
|
if (sortType === "Namespace") {
|
||||||
|
data = {
|
||||||
|
Namespace: {
|
||||||
|
name: namespaceName!,
|
||||||
|
direction: sortDirection
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
data = {
|
||||||
|
[sortType]: sortDirection
|
||||||
|
} as SortKeyData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SortKey(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
if (this.sortType == "Namespace") {
|
||||||
|
return `${this.sortType} '${this.namespaceName}' ${this.getDirectionString()}`;
|
||||||
|
} else {
|
||||||
|
return `${this.sortType} ${this.getDirectionString()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDirectionString(): string {
|
||||||
|
return this.sortDirection === "Ascending" ? "▲" : "▼";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
import {SortKey} from "./SortKey";
|
||||||
|
import {SortingPresetData} from "../api-types/presets";
|
||||||
|
import {mapNew} from "./adaptors";
|
||||||
|
|
||||||
|
export class SortingPreset {
|
||||||
|
private keys: SortKey[];
|
||||||
|
|
||||||
|
constructor(presetData: SortingPresetData) {
|
||||||
|
this._id = presetData.id;
|
||||||
|
this.keys = presetData.keys.map(mapNew(SortKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _id: number;
|
||||||
|
|
||||||
|
public get id(): number {
|
||||||
|
return this._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set id(value: number) {
|
||||||
|
this._id = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get sortKeys(): SortKey[] {
|
||||||
|
return this.keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get rawData(): SortingPresetData {
|
||||||
|
return {
|
||||||
|
id: this._id,
|
||||||
|
keys: this.keys.map(k => k.rawData),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromValues(id: number, keys: SortKey[]) {
|
||||||
|
let preset = new SortingPreset({ id, keys: [] });
|
||||||
|
preset.keys = keys;
|
||||||
|
return preset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setData(data: SortingPresetData) {
|
||||||
|
this._id = data.id;
|
||||||
|
this.keys = data.keys.map(mapNew(SortKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return this.sortKeys.join(", ");
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,14 @@
|
|||||||
<mat-drawer-container autosize>
|
<mat-drawer-container autosize>
|
||||||
<mat-drawer disableClose="true" mode="side" opened>
|
<mat-drawer disableClose="true" mode="side" opened>
|
||||||
<app-import-tab-sidebar [selectedFiles]="selectedFiles" (fileImported)="this.addFileFromImport($event)"
|
<app-import-tab-sidebar (fileImported)="this.addFileFromImport($event)"
|
||||||
(importFinished)="this.refreshFileView()"></app-import-tab-sidebar>
|
(importFinished)="this.refreshFileView()"
|
||||||
|
[selectedFiles]="selectedFiles"></app-import-tab-sidebar>
|
||||||
</mat-drawer>
|
</mat-drawer>
|
||||||
<mat-drawer-content>
|
<mat-drawer-content>
|
||||||
<app-file-multiview [mode]="this.state.mode.value" (modeChangeEvent)="this.state.mode.next($event)"
|
<app-file-multiview (fileSelectEvent)="this.onFileSelect($event)"
|
||||||
[preselectedFile]="this.getSelectedFileFromState()"
|
(modeChangeEvent)="this.state.mode.next($event)"
|
||||||
(fileSelectEvent)="this.onFileSelect($event)" [files]="this.files"></app-file-multiview>
|
[files]="this.files"
|
||||||
|
[mode]="this.state.mode.value"
|
||||||
|
[preselectedFile]="this.getSelectedFileFromState()"></app-file-multiview>
|
||||||
</mat-drawer-content>
|
</mat-drawer-content>
|
||||||
</mat-drawer-container>
|
</mat-drawer-container>
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
<div class="repo-page-content" *ngIf="!selectedRepository">
|
<div *ngIf="!selectedRepository" class="repo-page-content">
|
||||||
<div class="add-repo-tools">
|
<div class="add-repo-tools">
|
||||||
<button (click)="openAddRepositoryDialog()" color="primary" mat-flat-button>Add Repository</button>
|
<button (click)="openAddRepositoryDialog()" color="primary" mat-flat-button>Add Repository</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="repository-list">
|
<div class="repository-list">
|
||||||
<div *ngFor="let repository of repositories" class="repository-container">
|
<div *ngFor="let repository of repositories" class="repository-container">
|
||||||
<app-repository-card [repository]="repository" (openEvent)="this.onOpenRepository($event)"></app-repository-card>
|
<app-repository-card (openEvent)="this.onOpenRepository($event)"
|
||||||
|
[repository]="repository"></app-repository-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="repo-details" *ngIf="selectedRepository">
|
<div *ngIf="selectedRepository" class="repo-details">
|
||||||
<app-repository-details-view [repository]="selectedRepository"></app-repository-details-view>
|
<app-repository-details-view [repository]="selectedRepository"></app-repository-details-view>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
<button (click)="this.appClick.emit()"
|
||||||
|
[matTooltipShowDelay]="1000"
|
||||||
|
[matTooltip]="selectedPreset.toString()"
|
||||||
|
class="sort-button"
|
||||||
|
mat-flat-button>
|
||||||
|
<i>Sort: </i>
|
||||||
|
<app-sort-preset-item [preset]="this.selectedPreset"></app-sort-preset-item>
|
||||||
|
</button>
|
@ -0,0 +1,9 @@
|
|||||||
|
@import "src/colors";
|
||||||
|
|
||||||
|
.sort-button {
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
background-color: $background-lighter-10;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import {ComponentFixture, TestBed} from "@angular/core/testing";
|
||||||
|
|
||||||
|
import {SortButtonComponent} from "./sort-button.component";
|
||||||
|
|
||||||
|
describe("SortButtonComponent", () => {
|
||||||
|
let component: SortButtonComponent;
|
||||||
|
let fixture: ComponentFixture<SortButtonComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [SortButtonComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SortButtonComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,17 @@
|
|||||||
|
import {ChangeDetectionStrategy, Component, EventEmitter, Input, Output} from "@angular/core";
|
||||||
|
import {SortingPreset} from "../../../../../../api/models/SortingPreset";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-sort-button",
|
||||||
|
templateUrl: "./sort-button.component.html",
|
||||||
|
styleUrls: ["./sort-button.component.scss"],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class SortButtonComponent {
|
||||||
|
|
||||||
|
@Input() selectedPreset!: SortingPreset;
|
||||||
|
@Output() appClick = new EventEmitter<void>();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
<span *ngFor="let key of this.preset.sortKeys" class="sort-key">
|
||||||
|
<span class="sort-key-type">{{key.sortType}}</span>
|
||||||
|
<span *ngIf="key.sortType === 'Namespace'" class="sort-key-namespace"> {{key.namespaceName}}</span>
|
||||||
|
<ng-icon *ngIf="key.sortDirection === 'Ascending'" class="sort-key-direction" name="matExpandLess"></ng-icon>
|
||||||
|
<ng-icon *ngIf="key.sortDirection === 'Descending'" class="sort-key-direction" name="matExpandMore"></ng-icon>
|
||||||
|
<span class="key-divider">| </span>
|
||||||
|
</span>
|
@ -0,0 +1,20 @@
|
|||||||
|
@import "src/colors";
|
||||||
|
|
||||||
|
|
||||||
|
.sort-key-direction {
|
||||||
|
font-size: 1.5em;
|
||||||
|
vertical-align: bottom;
|
||||||
|
margin-bottom: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-key-type {
|
||||||
|
color: $primary-lighter-50
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-key-namespace {
|
||||||
|
color: $accent-lighter-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-key:last-child .key-divider {
|
||||||
|
display: none;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import {ComponentFixture, TestBed} from "@angular/core/testing";
|
||||||
|
|
||||||
|
import {SortPresetItemComponent} from "./sort-preset-item.component";
|
||||||
|
|
||||||
|
describe("SortPresetItemComponent", () => {
|
||||||
|
let component: SortPresetItemComponent;
|
||||||
|
let fixture: ComponentFixture<SortPresetItemComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [SortPresetItemComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SortPresetItemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,16 @@
|
|||||||
|
import {ChangeDetectionStrategy, Component, Input} from "@angular/core";
|
||||||
|
import {SortingPreset} from "../../../../../../api/models/SortingPreset";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-sort-preset-item",
|
||||||
|
templateUrl: "./sort-preset-item.component.html",
|
||||||
|
styleUrls: ["./sort-preset-item.component.scss"],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class SortPresetItemComponent {
|
||||||
|
|
||||||
|
@Input() preset!: SortingPreset;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
}
|
@ -1,35 +0,0 @@
|
|||||||
export class SortKey {
|
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public sortType: "Namespace" | "FileName" | "FileSize" | "FileImportedTime" | "FileCreatedTime" | "FileChangeTime" | "FileType" | "NumTags",
|
|
||||||
public sortDirection: "Ascending" | "Descending",
|
|
||||||
public namespaceName: string | undefined
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public toString(): string {
|
|
||||||
if (this.sortType == "Namespace") {
|
|
||||||
return `${this.sortType} '${this.namespaceName}' ${this.sortDirection}`;
|
|
||||||
} else {
|
|
||||||
return `${this.sortType} ${this.sortDirection}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public toBackendType(): any {
|
|
||||||
|
|
||||||
if (this.sortType == "Namespace") {
|
|
||||||
return {
|
|
||||||
"Namespace": {
|
|
||||||
direction: this.sortDirection,
|
|
||||||
name: this.namespaceName
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
let returnObj: any = {};
|
|
||||||
returnObj[this.sortType] = this.sortDirection;
|
|
||||||
|
|
||||||
return returnObj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,16 @@
|
|||||||
|
import {TestBed} from "@angular/core/testing";
|
||||||
|
|
||||||
|
import {PresetService} from "./preset.service";
|
||||||
|
|
||||||
|
describe("PresetService", () => {
|
||||||
|
let service: PresetService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(PresetService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be created", () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,26 @@
|
|||||||
|
import {Injectable} from "@angular/core";
|
||||||
|
import {SortingPreset} from "../../../api/models/SortingPreset";
|
||||||
|
import {MediarepoApi} from "../../../api/Api";
|
||||||
|
import {mapMany, mapNew} from "../../../api/models/adaptors";
|
||||||
|
import {SortKey} from "../../../api/models/SortKey";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: "root"
|
||||||
|
})
|
||||||
|
export class PresetService {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getAllSortingPresets(): Promise<SortingPreset[]> {
|
||||||
|
return MediarepoApi.getAllSortingPresets().then(mapMany(mapNew(SortingPreset)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addSortingPreset(keys: SortKey[]): Promise<SortingPreset> {
|
||||||
|
return MediarepoApi.addSortingPreset({ sortKeys: keys.map(k => k.rawData) }).then(mapNew(SortingPreset));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteSortingPreset(id: number): Promise<void> {
|
||||||
|
return MediarepoApi.deleteSortingPreset({ id });
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.7 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue