mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Overhaul look and feel of folder select (#527)
This commit is contained in:
@@ -4,6 +4,10 @@ query Configuration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query Directories($path: String) {
|
query Directory($path: String) {
|
||||||
directories(path: $path)
|
directory(path: $path) {
|
||||||
}
|
path
|
||||||
|
parent
|
||||||
|
directories
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ type Query {
|
|||||||
"""Returns the current, complete configuration"""
|
"""Returns the current, complete configuration"""
|
||||||
configuration: ConfigResult!
|
configuration: ConfigResult!
|
||||||
"""Returns an array of paths for the given path"""
|
"""Returns an array of paths for the given path"""
|
||||||
directories(path: String): [String!]!
|
directory(path: String): Directory!
|
||||||
|
|
||||||
# Metadata
|
# Metadata
|
||||||
|
|
||||||
|
|||||||
@@ -117,3 +117,10 @@ type ConfigResult {
|
|||||||
general: ConfigGeneralResult!
|
general: ConfigGeneralResult!
|
||||||
interface: ConfigInterfaceResult!
|
interface: ConfigInterfaceResult!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""Directory structure of a path"""
|
||||||
|
type Directory {
|
||||||
|
path: String!
|
||||||
|
parent: String
|
||||||
|
directories: [String!]!
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,12 +12,18 @@ func (r *queryResolver) Configuration(ctx context.Context) (*models.ConfigResult
|
|||||||
return makeConfigResult(), nil
|
return makeConfigResult(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) Directories(ctx context.Context, path *string) ([]string, error) {
|
func (r *queryResolver) Directory(ctx context.Context, path *string) (*models.Directory, error) {
|
||||||
var dirPath = ""
|
var dirPath = ""
|
||||||
if path != nil {
|
if path != nil {
|
||||||
dirPath = *path
|
dirPath = *path
|
||||||
}
|
}
|
||||||
return utils.ListDir(dirPath), nil
|
currentDir := utils.GetDir(dirPath)
|
||||||
|
|
||||||
|
return &models.Directory{
|
||||||
|
Path: currentDir,
|
||||||
|
Parent: utils.GetParent(currentDir),
|
||||||
|
Directories: utils.ListDir(currentDir),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeConfigResult() *models.ConfigResult {
|
func makeConfigResult() *models.ConfigResult {
|
||||||
|
|||||||
@@ -96,15 +96,6 @@ func EmptyDir(path string) error {
|
|||||||
|
|
||||||
// ListDir will return the contents of a given directory path as a string slice
|
// ListDir will return the contents of a given directory path as a string slice
|
||||||
func ListDir(path string) []string {
|
func ListDir(path string) []string {
|
||||||
if path == "" {
|
|
||||||
path = GetHomeDirectory()
|
|
||||||
}
|
|
||||||
|
|
||||||
absolutePath, err := filepath.Abs(path)
|
|
||||||
if err == nil {
|
|
||||||
path = absolutePath
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(path)
|
files, err := ioutil.ReadDir(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
path = filepath.Dir(path)
|
path = filepath.Dir(path)
|
||||||
@@ -133,3 +124,25 @@ func GetHomeDirectory() string {
|
|||||||
}
|
}
|
||||||
return currentUser.HomeDir
|
return currentUser.HomeDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetDir(path string) string {
|
||||||
|
if path == "" {
|
||||||
|
path = GetHomeDirectory()
|
||||||
|
}
|
||||||
|
|
||||||
|
absolutePath, err := filepath.Abs(path)
|
||||||
|
if err == nil {
|
||||||
|
path = absolutePath
|
||||||
|
}
|
||||||
|
return absolutePath
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetParent(path string) *string {
|
||||||
|
isRoot := path[len(path)-1:] == "/"
|
||||||
|
if isRoot {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
parentPath := filepath.Clean(path + "/..")
|
||||||
|
return &parentPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
import { Button, InputGroup, Form, Modal } from "react-bootstrap";
|
import { Button, InputGroup, Form, Modal } from "react-bootstrap";
|
||||||
import { LoadingIndicator } from "src/components/Shared";
|
import { LoadingIndicator } from "src/components/Shared";
|
||||||
import { useDirectories } from "src/core/StashService";
|
import { useDirectory } from "src/core/StashService";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
directories: string[];
|
directories: string[];
|
||||||
@@ -12,13 +13,18 @@ export const FolderSelect: React.FC<IProps> = (props: IProps) => {
|
|||||||
const [currentDirectory, setCurrentDirectory] = useState<string>("");
|
const [currentDirectory, setCurrentDirectory] = useState<string>("");
|
||||||
const [isDisplayingDialog, setIsDisplayingDialog] = useState<boolean>(false);
|
const [isDisplayingDialog, setIsDisplayingDialog] = useState<boolean>(false);
|
||||||
const [selectedDirectories, setSelectedDirectories] = useState<string[]>([]);
|
const [selectedDirectories, setSelectedDirectories] = useState<string[]>([]);
|
||||||
const { data, error, loading } = useDirectories(currentDirectory);
|
const { data, error, loading } = useDirectory(currentDirectory);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedDirectories(props.directories);
|
setSelectedDirectories(props.directories);
|
||||||
}, [props.directories]);
|
}, [props.directories]);
|
||||||
|
|
||||||
const selectableDirectories: string[] = data?.directories ?? [];
|
useEffect(() => {
|
||||||
|
if (currentDirectory === "" && data?.directory.path)
|
||||||
|
setCurrentDirectory(data.directory.path);
|
||||||
|
}, [currentDirectory, data]);
|
||||||
|
|
||||||
|
const selectableDirectories: string[] = data?.directory.directories ?? [];
|
||||||
|
|
||||||
function onSelectDirectory() {
|
function onSelectDirectory() {
|
||||||
selectedDirectories.push(currentDirectory);
|
selectedDirectories.push(currentDirectory);
|
||||||
@@ -36,6 +42,19 @@ export const FolderSelect: React.FC<IProps> = (props: IProps) => {
|
|||||||
props.onDirectoriesChanged(newSelectedDirectories);
|
props.onDirectoriesChanged(newSelectedDirectories);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const topDirectory = data?.directory?.parent ? (
|
||||||
|
<li className="folder-list-parent folder-list-item">
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
onClick={() =>
|
||||||
|
data.directory.parent && setCurrentDirectory(data.directory.parent)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FormattedMessage defaultMessage="Up a directory" id="up-dir" />
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
) : null;
|
||||||
|
|
||||||
function renderDialog() {
|
function renderDialog() {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -52,11 +71,11 @@ export const FolderSelect: React.FC<IProps> = (props: IProps) => {
|
|||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
setCurrentDirectory(e.currentTarget.value)
|
setCurrentDirectory(e.currentTarget.value)
|
||||||
}
|
}
|
||||||
defaultValue={currentDirectory}
|
value={currentDirectory}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
<InputGroup.Append>
|
<InputGroup.Append>
|
||||||
{!data || !data.directories || loading ? (
|
{!data || !data.directory || loading ? (
|
||||||
<LoadingIndicator inline />
|
<LoadingIndicator inline />
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
@@ -64,12 +83,12 @@ export const FolderSelect: React.FC<IProps> = (props: IProps) => {
|
|||||||
</InputGroup.Append>
|
</InputGroup.Append>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
<ul className="folder-list">
|
<ul className="folder-list">
|
||||||
|
{topDirectory}
|
||||||
{selectableDirectories.map((path) => {
|
{selectableDirectories.map((path) => {
|
||||||
return (
|
return (
|
||||||
<li key={path} className="folder-item">
|
<li key={path} className="folder-list-item">
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
key={path}
|
|
||||||
onClick={() => setCurrentDirectory(path)}
|
onClick={() => setCurrentDirectory(path)}
|
||||||
>
|
>
|
||||||
{path}
|
{path}
|
||||||
|
|||||||
@@ -75,3 +75,41 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.folder-list {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 1rem;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.btn-link {
|
||||||
|
border: none;
|
||||||
|
color: black;
|
||||||
|
font-weight: 400;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child::before {
|
||||||
|
content: "└ \1F4C1";
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "├ \1F4C1";
|
||||||
|
display: inline-block;
|
||||||
|
padding-right: 1rem;
|
||||||
|
transform: scale(1.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-parent {
|
||||||
|
&::before {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-link {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -173,8 +173,8 @@ export const useLatestVersion = () =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const useConfiguration = () => GQL.useConfigurationQuery();
|
export const useConfiguration = () => GQL.useConfigurationQuery();
|
||||||
export const useDirectories = (path?: string) =>
|
export const useDirectory = (path?: string) =>
|
||||||
GQL.useDirectoriesQuery({ variables: { path } });
|
GQL.useDirectoryQuery({ variables: { path } });
|
||||||
|
|
||||||
export const performerMutationImpactedQueries = [
|
export const performerMutationImpactedQueries = [
|
||||||
"findPerformers",
|
"findPerformers",
|
||||||
|
|||||||
Reference in New Issue
Block a user